1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-13 11:12:04 +00:00

Compare commits

...

326 Commits

Author SHA1 Message Date
J. Nick Koston
744b8af2c7 disable loop when unused 2026-01-03 13:44:32 -10:00
J. Nick Koston
095f369ebd Merge branch 'dev' into wh_template 2026-01-03 13:30:51 -10:00
Clyde Stubbs
c29aa61e2a [image] Use alternative version of CairoSVG on Windows (#12811) 2026-01-04 10:08:47 +11:00
J. Nick Koston
cb3edfc654 [wifi] Use stack-based MAC formatting in ESP8266 and IDF event handlers (#12834) 2026-01-03 12:32:22 -10:00
J. Nick Koston
6685fa1da9 [core] Fix startup delay from setup timing logs when console connected (#12832) 2026-01-03 12:32:10 -10:00
J. Nick Koston
d505f0316b [wifi] Combine scan result log lines to reduce loop blocking with many matching APs (#12830) 2026-01-03 12:31:58 -10:00
J. Nick Koston
9781073f2a [espnow] Use stack-based MAC formatting and remove dead code (#12836) 2026-01-03 12:31:38 -10:00
John Hollowell
0a0501c140 Fix comment typos (#12828) 2026-01-03 17:11:48 -05:00
Jasper van der Neut - Stulen
a6e9aa7876 [mhz19] Refactor Actions to Parented (#12837) 2026-01-03 17:11:02 -05:00
Conrad Juhl Andersen
ede7391582 [wts01] Fix negative values for WTS01 sensor (#12835) 2026-01-03 17:06:33 -05:00
Jasper van der Neut - Stulen
5cfcf8d104 [mhz19] Make detection range configurable (#12677)
Co-authored-by: Fabio Pugliese Ornellas <fabio.ornellas@gmail.com>
2026-01-03 15:51:48 -05:00
J. Nick Koston
c34665f650 [api] Fix KeyError when running logs after password removal (#12831) 2026-01-03 19:13:07 +00:00
Mariusz Kryński
69867bf818 [nrf52, zephyr] move nrf52-specific code to nrf52 component (#12582)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
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@home-assistant.io>
2026-01-03 18:58:56 +00:00
J. Nick Koston
1d323c2d71 [api] Remove deprecated password authentication (#12819) 2026-01-03 07:14:48 -10:00
tomaszduda23
95a7356ea0 [uart] make sure that all variables are initialized (#12823) 2026-01-03 03:43:17 -06:00
J. Nick Koston
89b550b74a [tests] Remove reserved / character from entity names in component tests (#12820) 2026-01-03 01:00:46 -06:00
dependabot[bot]
538c6544a0 Bump ruamel-yaml from 0.18.17 to 0.19.1 (#12768)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-02 20:51:56 -10:00
dependabot[bot]
98e3695c89 Bump aioesphomeapi from 43.9.1 to 43.10.0 (#12821)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-03 06:45:17 +00:00
J. Nick Koston
00fd4f2fdd [esp8266] Exclude unused waveform code to save ~596 bytes RAM (#12690) 2026-01-02 19:51:07 -10:00
J. Nick Koston
2a5be725c8 [api] Enable zero-copy bytes SOURCE_BOTH messages (#12816) 2026-01-02 19:50:30 -10:00
Robert Klep
c4d339a4c9 [core] Add CONF_ON_START (#12439) (#12440) 2026-01-02 23:42:18 -05:00
J. Nick Koston
6409970f6e [uponor_smatrix] Use stack-based hex formatting in verbose logging (#12797)
Co-authored-by: Stefan Rado <628587+kroimon@users.noreply.github.com>
2026-01-02 16:41:02 -10:00
J. Nick Koston
bc1af007b4 [vbus] Use stack-based hex formatting in verbose logging (#12796) 2026-01-02 16:40:47 -10:00
Thomas Rupprecht
c3ffc1635d [gps] add icon for HDOP and use correct state_class for longitude and… (#12718) 2026-01-02 21:40:28 -05:00
J. Nick Koston
016eeef04a [tee501] Use stack-based hex formatting in verbose logging (#12795) 2026-01-02 16:40:06 -10:00
J. Nick Koston
ace48464a8 [addressable_light] Use StringRef to avoid allocation when saving effect name (#12759) 2026-01-02 16:39:44 -10:00
J. Nick Koston
64ba376330 [hte501] Use stack-based hex formatting in verbose logging (#12794) 2026-01-02 16:37:38 -10:00
J. Nick Koston
d946ddabfd [xiaomi_ble] Use stack-based hex formatting in verbose logging (#12793) 2026-01-02 16:37:16 -10:00
J. Nick Koston
a57011b50b [kuntze] Use stack buffer for hex formatting in verbose logging (#12775) 2026-01-02 16:36:57 -10:00
J. Nick Koston
1240e7907e [api] Use stack-based format_hex_pretty_to for packet logging macros (#12788) 2026-01-02 16:35:44 -10:00
J. Nick Koston
f0391f0213 [api] Remove object_id from API protocol - clients compute it from name #12698 (#12818) 2026-01-02 16:32:46 -10:00
J. Nick Koston
3cc6810be5 [core] Remove object_id RAM storage - no longer in hot path after #12627 (#12631) 2026-01-02 15:46:01 -10:00
J. Nick Koston
916370a943 [gpio] Avoid heap allocation in dump_summary (#12760) 2026-01-02 15:42:56 -10:00
J. Nick Koston
e2f45c590e [esp32_improv] Use stack buffer for hex formatting in verbose logging (#12737) 2026-01-02 14:28:38 -10:00
J. Nick Koston
7d21411ca4 [epaper_spi] Use stack buffer for hex formatting in command logging (#12734) 2026-01-02 14:27:00 -10:00
J. Nick Koston
56ed5af27d [nextion] Use stack buffers for hex formatting in upload logging (#12733) 2026-01-02 14:26:14 -10:00
J. Nick Koston
c8241b0122 [sonoff_d1] Use stack buffer for hex formatting in logging (#12730) 2026-01-02 14:25:02 -10:00
J. Nick Koston
30efd7fb07 [jsn_sr04t] Use stack buffer for hex formatting in error logging (#12729) 2026-01-02 14:24:47 -10:00
J. Nick Koston
1703343694 [a02yyuw] Use stack buffer for hex formatting in error logging (#12728) 2026-01-02 14:24:30 -10:00
J. Nick Koston
7fa04b6c25 [a01nyub] Use stack buffer for hex formatting in error logging (#12727) 2026-01-02 14:23:33 -10:00
J. Nick Koston
61b6476de4 [opentherm] Replace heap-allocating format calls with printf format specifiers in debug_error (#12726) 2026-01-02 14:23:18 -10:00
J. Nick Koston
b4e5e0bc9b [rc522] Use stack buffers for hex formatting in tag logging (#12725) 2026-01-02 14:22:58 -10:00
J. Nick Koston
f9b4e0e489 [remote_base] Use stack buffer for hex formatting in haier protocol logging (#12723) 2026-01-02 14:22:26 -10:00
J. Nick Koston
9ccb100cca [remote_base] Use stack buffer for hex formatting in mirage protocol logging (#12722) 2026-01-02 14:21:42 -10:00
J. Nick Koston
20b66cba23 [shelly_dimmer] Use stack buffer for hex formatting in command logging (#12721) 2026-01-02 14:21:23 -10:00
J. Nick Koston
b711172b33 [wifi] Use precision format specifier for SSID logging to avoid stack copy (#12704) 2026-01-02 14:21:09 -10:00
J. Nick Koston
0c4184b129 [cse7766] Use stack buffer for hex formatting in debug logging (#12732) 2026-01-02 14:20:17 -10:00
J. Nick Koston
0e108c2178 [esp32] Add minimum_chip_revision setting and log chip revision at startup (#12696) 2026-01-02 14:14:52 -10:00
J. Nick Koston
2230e56347 [wifi] Use stack buffers for IP address logging to avoid heap allocations (#12680) 2026-01-02 14:14:24 -10:00
J. Nick Koston
2ff9535f5f [esp32_improv] Use stack buffer for URL formatting to avoid heap allocation (#12682) 2026-01-02 14:14:12 -10:00
J. Nick Koston
ddb6c6cfd4 [captive_portal] Use stack buffer for IP address logging in DNS server (#12679) 2026-01-02 14:13:59 -10:00
J. Nick Koston
00ab64a3c7 [wifi] Use wifi_ssid_to() to avoid heap allocations in automation and connection checks (#12678) 2026-01-02 14:13:43 -10:00
J. Nick Koston
e732f8469e [udp] Avoid heap allocations when joining multicast groups (#12685) 2026-01-02 14:13:26 -10:00
J. Nick Koston
023be88a87 [tuya] Use stack buffers for hex logging to avoid heap allocations (#12689) 2026-01-02 14:13:08 -10:00
J. Nick Koston
25e60d62cf [mqtt] Avoid heap allocations when logging IP addresses (#12686) 2026-01-02 14:12:04 -10:00
J. Nick Koston
167a42aa27 [api] Use StringRef in send_action_response and send_execute_service_response (#12658) 2026-01-02 14:11:45 -10:00
J. Nick Koston
0ef49a8b73 [ld2410][ld2412][ld2450] Use stack buffers for hex logging (#12688) 2026-01-02 14:11:31 -10:00
J. Nick Koston
51259888bf [voice_assistant] Use zero-copy buffer access for audio data (#12656) 2026-01-02 14:10:21 -10:00
J. Nick Koston
0b7ff09657 [api] Use pointer to FixedVector for siren tones field (#12657) 2026-01-02 14:09:40 -10:00
J. Nick Koston
f394cf3f4d [packet_transport] Use stack-based format_hex_pretty_to for logging (#12791) 2026-01-02 14:06:03 -10:00
J. Nick Koston
4cb066bcbf [api] Use StringRef in handle_action_response to avoid temporary string (#12655) 2026-01-02 14:05:50 -10:00
J. Nick Koston
e7001c5eea [api] Auto-generate zero-copy pointer access for incoming API bytes fields (#12654) 2026-01-02 14:05:37 -10:00
esphomebot
5bb9ffa0cb Update webserver local assets to 20260102-230255 (#12817) 2026-01-02 23:14:11 +00:00
J. Nick Koston
c6713eaccb [web_server] Fix URL collisions with UTF-8 names and sub-devices (#12627) 2026-01-02 13:07:11 -10:00
Jonathan Swoboda
087f521b19 [ultrasonic] Use interrupt-based measurement for reliability (#12617)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-02 15:58:53 -05:00
Jonathan Swoboda
763515d3a1 [core] Remove unused USE_ESP32_FRAMEWORK_ARDUINO ifdefs (#12813)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-02 14:47:14 -05:00
J. Nick Koston
6d4f4d8d23 [api] Auto-generate StringRef for incoming API string fields (#12648) 2026-01-02 08:17:05 -10:00
Tobias Stanzel
d7fd85e610 [spi] Allow any achievable data rate (#12753)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-01-02 18:10:30 +11:00
J. Nick Koston
8acaa16987 [usb_cdc_acm] Use stack-based hex formatting in verbose logging (#12792) 2026-01-02 01:04:11 -06:00
J. Nick Koston
4e8c02b396 [xiaomi_*] Use stack-based hex formatting for bindkey logging (#12798) 2026-01-01 20:25:12 -10:00
J. Nick Koston
a828abf53d [ota] Remove MD5 authentication support (#12707) 2026-01-01 20:24:31 -10:00
J. Nick Koston
ebfa0149cc [light] Use StringRef to avoid allocation in JSON effect name serialization (#12758) 2026-01-01 20:23:37 -10:00
J. Nick Koston
3a4cca0027 [ble_client] Use stack buffer for hex formatting in very verbose logging (#12744) 2026-01-01 20:22:48 -10:00
J. Nick Koston
7702a9ae85 [ethernet] Use stack buffer for hex formatting in very verbose logging (#12742) 2026-01-01 20:22:19 -10:00
J. Nick Koston
2e8baa0493 [esp32_ble_tracker] Use stack buffer for hex formatting in very verbose logging (#12741) 2026-01-01 20:21:33 -10:00
J. Nick Koston
69ec311d21 [hlk_fm22x] Use stack buffer for hex formatting in verbose logging (#12740) 2026-01-01 20:20:58 -10:00
J. Nick Koston
1cc18055ef [i2c] Use stack buffer for hex formatting in verbose logging (#12739) 2026-01-01 20:20:24 -10:00
J. Nick Koston
bcc6bbbf5f [espnow] Use stack buffer for hex formatting in verbose logging (#12738) 2026-01-01 20:19:49 -10:00
J. Nick Koston
71c3d4ca27 [mopeka_std_check] Use stack-based format_hex_pretty_to for very verbose logging (#12790) 2026-01-01 20:19:20 -10:00
J. Nick Koston
c6f3860f90 [ee895] Use stack-based format_hex_to for verbose logging (#12789) 2026-01-01 20:18:23 -10:00
J. Nick Koston
0049c8ad38 [zwave_proxy] Use stack-based format_hex_pretty_to for very verbose logging (#12786) 2026-01-01 20:17:51 -10:00
J. Nick Koston
e1788bba45 [seeed_mr60fda2] Use stack-based format_hex_pretty_to for verbose logging (#12785) 2026-01-01 20:17:22 -10:00
J. Nick Koston
4fcd263ea8 [seeed_mr60bha2] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12784) 2026-01-01 20:16:40 -10:00
J. Nick Koston
c81ce243cc [qspi_dbi] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12783) 2026-01-01 20:13:10 -10:00
J. Nick Koston
7df41124b2 [pn532_spi] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12782) 2026-01-01 20:11:53 -10:00
J. Nick Koston
b5188731f8 [modbus] Use stack buffer for hex formatting in verbose logging (#12780) 2026-01-01 20:10:45 -10:00
J. Nick Koston
0924281545 [mitsubishi] Use stack buffer for hex formatting in verbose logging (#12779) 2026-01-01 20:10:08 -10:00
J. Nick Koston
14e97642f7 [mipi_rgb] Use stack buffer for hex formatting in init sequence logging (#12777) 2026-01-01 20:09:37 -10:00
J. Nick Koston
544aaeaa66 [mipi_dsi] Use stack buffer for hex formatting in very verbose logging (#12776) 2026-01-01 20:08:57 -10:00
Stuart Parmenter
7483bbd6ea [display] Ensure drivers respect clipping during fill() (#12808) 2026-01-02 16:34:39 +11:00
Artur
2841b5fe44 [sn74hc595]: fix 'Attempted read from write-only channel' when using esp-idf framework (#12801) 2026-01-01 23:28:10 -05:00
J. Nick Koston
ed435241b1 [mipi_spi] Use stack buffer for hex formatting in verbose logging (#12778) 2026-01-01 11:48:37 -10:00
H. Árkosi Róbert
9847e51fbc [bthome_mithermometer] Add BTHome parsing for Xiaomi Mijia BLE Sensors (#12635) 2026-01-02 08:40:18 +11:00
dependabot[bot]
dc320f455a Bump bleak from 2.1.0 to 2.1.1 (#12804)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 09:16:01 -10:00
Clyde Stubbs
1945e85ddc [core] Make LockFreeQueue more widely available (#12766) 2026-01-01 22:07:35 +11:00
Clyde Stubbs
4313130f2e [lvgl] Fix arc background angles (#12773) 2026-01-01 14:44:21 +11:00
Jonathan Swoboda
3c9ed126a6 Merge branch 'release' into dev 2025-12-31 17:42:51 -05:00
Jonathan Swoboda
d8c23d4fc9 Merge pull request #12772 from esphome/bump-2025.12.4
2025.12.4
2025-12-31 17:42:39 -05:00
Konstantin Tretyakov
1d96de986e [sdist] Include yaml files in components in source distribution package
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-01-01 08:49:43 +11:00
Jonathan Swoboda
e9e0712959 Bump version to 2025.12.4 2025-12-31 16:07:00 -05:00
J. Nick Koston
062840dd7b [docker] Add build-essential to fix ruamel.yaml 0.19.0 compilation (#12769)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-31 16:07:00 -05:00
J. Nick Koston
f0f01c081a [wifi] Fix ESP-IDF reporting connected before DHCP completes on reconnect (#12755) 2025-12-31 16:07:00 -05:00
Stuart Parmenter
dd855985be [hub75] Add clipping check (#12762) 2025-12-31 16:06:59 -05:00
J. Nick Koston
4633803d5d [docker] Add build-essential to fix ruamel.yaml 0.19.0 compilation (#12769)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-31 16:05:58 -05:00
J. Nick Koston
476d00d0e5 [wifi] Fix ESP-IDF reporting connected before DHCP completes on reconnect (#12755) 2025-12-31 15:59:28 -05:00
Stuart Parmenter
98cdef2568 [hub75] Add clipping check (#12762) 2025-12-31 15:58:37 -05:00
J. Nick Koston
bd3ecad3a1 [core] Add format_hex_pretty_to buffer helper and reduce code duplication (#12687) 2025-12-30 11:51:51 -10:00
J. Nick Koston
dae7ba604a [ethernet_info] Eliminate heap allocations in DNS text sensor (#12756) 2025-12-30 10:25:51 -10:00
Jonathan Swoboda
96c47f3b4d Merge branch 'release' into dev 2025-12-30 09:31:44 -05:00
Jonathan Swoboda
5b5cede5f9 Merge pull request #12752 from esphome/bump-2025.12.3
2025.12.3
2025-12-30 09:31:31 -05:00
Jonathan Swoboda
c737033cc4 Bump version to 2025.12.3 2025-12-30 09:22:03 -05:00
J. Nick Koston
0194bfd9ea [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) 2025-12-30 09:22:03 -05:00
J. Nick Koston
339399eb70 [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) 2025-12-30 09:22:03 -05:00
Samuel Sieb
a615b28ecf [bme68x_bsec2] add id: to allow extending (#12649) 2025-12-29 23:22:36 -08:00
bakroistvan
468bd7b04f [dallas_temp] higher precision for logged temperature (#12695) 2025-12-29 22:53:28 -08:00
Jonathan Swoboda
4c16afeacb [esp32] Add IDF framework source for Arduino builds (#12731)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-29 22:25:26 -05:00
J. Nick Koston
d86c05bfe6 [esp32] Breaking Change: Change default framework to ESP-IDF (#12746)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-30 03:23:41 +00:00
J. Nick Koston
63464a13c3 [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) 2025-12-29 16:57:22 -10:00
Clyde Stubbs
20e43398fa [cli] Report program path on host (#12743) 2025-12-30 13:21:30 +11:00
hsand
2e7cdad532 [pvvx_mithermometer] fix displaying negative numbers (#12735) 2025-12-29 13:58:38 -08:00
dependabot[bot]
636cccc6a3 Bump aioesphomeapi from 43.9.0 to 43.9.1 (#12724)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 09:55:26 -10:00
Thomas Rupprecht
93e2a1bd1a [tests] improve mipi_spi variable naming (#12716) 2025-12-29 14:21:07 -05:00
Thomas Rupprecht
dd3beb5841 [tests] fix typo mipi tests (#12715) 2025-12-29 14:20:38 -05:00
Thomas Rupprecht
97af01c5ed [usb_host] sort esp32 variants (#12720) 2025-12-29 14:19:36 -05:00
J. Nick Koston
7e362cdafc [ota] Use precision format specifier for auth logging (#12706)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-29 08:43:54 -10:00
Jonathan Swoboda
890d531cea [esp32] Bump to ESP-IDF 5.5.2, Arduino 3.3.5, platform 55.3.35 (#12681)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-29 11:35:54 -05:00
Swaptor
6a6c6b648f [internal_temperature] Add ESP32-C5 support (#12713)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-12-29 11:32:32 -05:00
dependabot[bot]
d0673122a8 Bump aioesphomeapi from 43.8.0 to 43.9.0 (#12702) 2025-12-28 18:15:06 -10:00
dependabot[bot]
5cbef3ef95 Bump aioesphomeapi from 43.7.0 to 43.8.0 (#12701) 2025-12-29 03:15:40 +00:00
dependabot[bot]
a1e0121330 Bump bleak from 2.0.0 to 2.1.0 (#12700)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 16:48:20 -10:00
dependabot[bot]
eb050ff13e Bump aioesphomeapi from 43.6.0 to 43.7.0 (#12699)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 16:48:08 -10:00
Douwe
90fa016b2b Merge branch 'dev' into wh_template 2025-12-28 11:46:41 +01:00
Jonathan Swoboda
45e61f100c [core] Replace USE_ESP_IDF with USE_ESP32 across components (#12673)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-27 11:59:55 -10:00
J. Nick Koston
5e99dd14ae [ethernet] Eliminate heap allocations in dump_config logging (#12665) 2025-12-27 08:36:35 -10:00
J. Nick Koston
a6097f4a0f [wifi] Eliminate heap allocations in dump_config logging (#12664)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-27 08:36:19 -10:00
J. Nick Koston
f243e609a5 [wifi] Use StringRef and std::span in WiFiConnectStateListener to avoid allocations (#12672) 2025-12-27 08:35:58 -10:00
J. Nick Koston
be0bf1e5b9 [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) 2025-12-27 08:35:36 -10:00
J. Nick Koston
a275f37135 [udp] Use stack buffer for listen address logging in dump_config (#12667) 2025-12-27 08:35:16 -10:00
J. Nick Koston
e9f2d75aab [core] Add format_hex_to helper for zero-allocation hex formatting (#12670) 2025-12-27 08:34:45 -10:00
J. Nick Koston
34067f8b15 [esp8266] Native OTA backend to reduce Arduino dependencies (#12675)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-27 08:29:15 -10:00
J. Nick Koston
bdc087148a [wifi_info] Reduce heap allocations in text sensor formatting (#12660) 2025-12-26 12:52:41 -10:00
J. Nick Koston
5a2e0612a8 [web_server] Use C++17 nested namespace syntax (#12663) 2025-12-26 08:44:34 -10:00
J. Nick Koston
f1fecd22e3 [web_server] Move HTTP header strings to flash on ESP8266 (#12668) 2025-12-26 08:44:17 -10:00
J. Nick Koston
0919017d49 [wifi] Avoid unnecessary string copy in failed connection logging (#12659) 2025-12-26 08:44:03 -10:00
J. Nick Koston
963f594c9e [text_sensor] Return state by const reference to avoid copies (#12661) 2025-12-26 07:58:46 -10:00
J. Nick Koston
4f70663658 [alarm_control_panel] Use C++17 nested namespace and remove unused include (#12662) 2025-12-26 07:57:33 -10:00
dependabot[bot]
958a35e262 Bump aioesphomeapi from 43.5.0 to 43.6.0 (#12644)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 14:17:52 -10:00
J. Nick Koston
0c566c6f00 [core] Deprecate get_object_id() and migrate remaining usages to get_object_id_to() (#12629) 2025-12-23 06:59:07 -10:00
Jonathan Swoboda
ba73289b28 Merge branch 'release' into dev 2025-12-23 11:17:15 -05:00
Jonathan Swoboda
99f7e9aeb7 Merge pull request #12632 from esphome/bump-2025.12.2
2025.12.2
2025-12-23 11:17:01 -05:00
Jonathan Swoboda
ebb6babb3d Fix hash 2025-12-23 09:26:38 -05:00
Jonathan Swoboda
0922f240e0 Bump version to 2025.12.2 2025-12-23 09:23:04 -05:00
Jonathan Swoboda
c8fb694dcb [cc1101] Fix packet mode RSSI/LQI (#12630)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 09:23:04 -05:00
J. Nick Koston
6054685dae [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) 2025-12-23 09:23:04 -05:00
Anna Oake
61ec3508ed [cc1101] Fix option defaults and move them to YAML (#12608) 2025-12-23 09:23:04 -05:00
Leo Bergolth
086ec770ea send NIL ("-") as timestamp if time source is not valid (#12588) 2025-12-23 09:23:04 -05:00
Stuart Parmenter
b055f5b4bf [hub75] Bump esp-hub75 version to 0.1.7 (#12564) 2025-12-23 09:23:00 -05:00
Eduard Llull
726db746c8 [display_menu_base] Call on_value_ after updating the select (#12584) 2025-12-23 09:21:54 -05:00
Keith Burzinski
1922455fa7 [wifi] Fix for wifi_info when static IP is configured (#12576) 2025-12-23 09:21:54 -05:00
Thomas Rupprecht
dc943d7e7a [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-23 09:21:54 -05:00
Jonathan Swoboda
ffefa8929e [cc1101] Fix packet mode RSSI/LQI (#12630)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 09:05:48 -05:00
J. Nick Koston
7d5342bca5 [logger] Host: Use fwrite() with explicit length and remove platform branching (#12628) 2025-12-22 16:45:22 -10:00
J. Nick Koston
b4c92dd8cb [logger] Zephyr: Use k_str_out() with known length instead of printk() (#12619) 2025-12-22 14:29:47 -10:00
eoasmxd
1b31253287 Add Event Component to UART (#11765)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-22 12:19:48 -10:00
J. Nick Koston
af0d4d2c2c [web_server] Use stack buffers for value formatting to reduce flash usage (#12575) 2025-12-22 21:56:07 +00:00
J. Nick Koston
f238f93312 [core] Move comment to PROGMEM on ESP8266 (#12554) 2025-12-22 21:37:51 +00:00
J. Nick Koston
bdbe72b7f1 [web_server] Make internal JSON helper methods private (#12624) 2025-12-22 11:14:11 -10:00
J. Nick Koston
c8b531ac06 [safe_mode] Defer preference sync in clean_rtc to avoid blocking event loop (#12625) 2025-12-22 11:13:51 -10:00
Jonathan Swoboda
918bc4b74f [esp32] Remove remaining using_esp_idf checks (#12623)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 15:41:14 -05:00
Keith Burzinski
08c0f65f30 [sprinkler] Remove internal latching valve support (#12603) 2025-12-22 14:13:18 -05:00
Keith Burzinski
cd45fe0c3a [thermostat] Optimizations to reduce binary size (#12621) 2025-12-22 14:13:03 -05:00
Jonathan Swoboda
84b5d9b21c [core] Remove deprecated config options from before 2025 (#12622)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 14:00:12 -05:00
J. Nick Koston
6383fe4598 [core] Add zero-allocation object_id methods (#12578) 2025-12-22 07:56:33 -10:00
J. Nick Koston
265ad9d264 [esp32_camera] Reduce loop overhead and improve frame latency with wake_loop_threadsafe (#12601) 2025-12-22 07:55:28 -10:00
J. Nick Koston
1bdbc4cb85 [esp32_ble] Avoid string allocation when setting BLE device name (#12579) 2025-12-22 07:54:55 -10:00
J. Nick Koston
1756fc31b0 [api] Use union for iterators to reduce APIConnection size by ~16 bytes (#12563) 2025-12-22 07:54:17 -10:00
J. Nick Koston
74b075d3cf [codegen] Add static storage class to global variables for size optimization (#12616) 2025-12-22 07:03:17 -10:00
Douwe
c7f9ab2127 Merge branch 'dev' into wh_template 2025-12-22 10:45:13 +01:00
Clint Armstrong
52eb08f48f [thermostat] Enhance timer behavior for immediate response to duration changes (#12610) 2025-12-21 23:52:17 -06:00
J. Nick Koston
0d993691d4 [logger] RP2040: Use write() with known length instead of println() (#12615) 2025-12-21 17:59:30 -10:00
J. Nick Koston
2c7369614e compile tests 2025-12-21 12:04:28 -10:00
J. Nick Koston
75cb7e0095 typing 2025-12-21 11:57:02 -10:00
J. Nick Koston
f9eb2dab37 typing 2025-12-21 11:50:51 -10:00
J. Nick Koston
9376e904a6 tweaks 2025-12-21 11:49:17 -10:00
J. Nick Koston
2fcaed08b2 update schema 2025-12-21 11:47:22 -10:00
J. Nick Koston
8dae96617d realign with step 1 2025-12-21 11:46:37 -10:00
J. Nick Koston
1e8e917af6 Merge branch 'dev' into wh_template 2025-12-21 11:36:44 -10:00
Douwe
39926909af [water_heater] (1/4) Implement API/Core/component for new water_heater component (#12498)
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+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-21 11:36:34 -10:00
J. Nick Koston
637e032528 [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) 2025-12-21 09:04:43 -10:00
Anna Oake
d89eaf5bf6 [cc1101] Fix option defaults and move them to YAML (#12608) 2025-12-21 13:04:17 -05:00
J. Nick Koston
bf617c3279 [web_server] Replace str_sprintf with stack buffers (#12592) 2025-12-21 07:32:05 -10:00
J. Nick Koston
c70eab931e [api] Add zero-copy support for Home Assistant state response messages (#12585) 2025-12-21 07:31:54 -10:00
J. Nick Koston
a799ac6488 [syslog] Eliminate heap allocations in log path (#12589) 2025-12-21 07:10:27 -10:00
polyfloyd
5a36cea5ec Add nix files to gitignore (#12604) 2025-12-21 09:26:03 -05:00
dhoeben
2b8b42cbd7 Fix imports 2025-12-21 13:41:24 +01:00
dhoeben
3e9e0a93e2 Add supported_modes to available options 2025-12-21 13:34:39 +01:00
dhoeben
5602b39744 Removed duplicate min_/max_temperature 2025-12-21 12:13:46 +01:00
Douwe
8d5b49188c Merge branch 'dev' into wh_template 2025-12-21 11:06:43 +01:00
J. Nick Koston
60756db06d [syslog] Use C++17 nested namespace syntax (#12594) 2025-12-21 02:47:37 -06:00
Keith Burzinski
2113858f89 [sprinkler] Squash a few bugs + minor optimization (#12436) 2025-12-21 02:45:24 -06:00
dependabot[bot]
e89fe9b945 Bump aioesphomeapi from 43.4.0 to 43.5.0 (#12599)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-20 23:59:48 +00:00
dependabot[bot]
f1362cd9fe Bump aioesphomeapi from 43.3.0 to 43.4.0 (#12597)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-20 22:37:10 +00:00
Frédéric Metrich
bf554a58ef [const] Add CONF_ON_DATA and consolidate definitions (#12595)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 12:17:09 -10:00
J. Nick Koston
644e806afd [zwave_proxy] Add missing USE_API guards for clang-tidy (#12590) 2025-12-20 10:40:43 -10:00
Leo Bergolth
6c2d255230 send NIL ("-") as timestamp if time source is not valid (#12588) 2025-12-20 10:04:59 -10:00
Stuart Parmenter
6f3bfc2060 [hub75] Bump esp-hub75 version to 0.1.7 (#12564) 2025-12-20 13:18:20 -05:00
J. Nick Koston
40eb898814 [api] Add zero-copy support for noise encryption key requests (#12405) 2025-12-20 06:47:30 -10:00
J. Nick Koston
64269334ce [text_sensor] Avoid string copies in callbacks by passing const ref (#12503)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-20 06:46:13 -10:00
Eduard Llull
121375ff39 [display_menu_base] Call on_value_ after updating the select (#12584) 2025-12-20 10:59:14 -05:00
J. Nick Koston
48cdf9e036 [tests] Fix race condition in alarm control panel state transitions test (#12581) 2025-12-20 10:47:29 -05:00
J. Nick Koston
3e313014e1 [core] Migrate entities to use lazy callbacks (#12580) 2025-12-19 19:04:21 -10:00
Martin Ebner
be6c1e4ec0 [sen5x][sgp4x] Move configuration keys from SEN5x and SGP4x to const.py (#12567)
Co-authored-by: Martin Ebner <martinebner@me.com>
2025-12-19 21:29:02 -05:00
Keith Burzinski
730bf206de [wifi] Fix for wifi_info when static IP is configured (#12576) 2025-12-19 21:25:16 -05:00
J. Nick Koston
c9fccdff25 [fan] Add zero-copy support for API preset mode commands (#12404)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-19 22:05:52 +00:00
J. Nick Koston
ada6c42f3f [alarm_control_panel] Remove redundant per-state callbacks (#12171)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-19 11:48:14 -10:00
J. Nick Koston
988b888c63 [ota] Replace std::function callbacks with listener interface (#12167) 2025-12-19 11:19:07 -10:00
J. Nick Koston
940afdbb12 [climate] Add zero-copy support for API custom fan mode and preset commands (#12402) 2025-12-19 11:18:50 -10:00
J. Nick Koston
81e91c2a8f [esp32_ble] Add stack-based UUID formatting to avoid heap allocations (#12510) 2025-12-19 11:18:32 -10:00
J. Nick Koston
ebc3d28ade [wifi] Replace optional with sentinel values to reduce RAM and clarify API (#12446) 2025-12-19 11:18:15 -10:00
Rene Guca
25cebedcfc [dht] Fix "Falling edge for bit 39 failed!" for Sonoff THS01 (#9225)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-19 15:42:39 -05:00
dependabot[bot]
98ed679b19 Bump ruff from 0.14.9 to 0.14.10 (#12572)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-19 19:22:56 +00:00
dependabot[bot]
59b38d79b4 Bump docker/setup-buildx-action from 3.11.1 to 3.12.0 in the docker-actions group (#12574)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 09:18:52 -10:00
dependabot[bot]
26c16f4ca2 Bump voluptuous from 0.15.2 to 0.16.0 (#12573)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 09:18:33 -10:00
Jas Strong
940e619481 [aqi, hm3301, pmsx003] Air Quality Index improvements (#12203)
Co-authored-by: jas <jas@asspa.in>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-12-19 13:42:11 -05:00
Jonathan Swoboda
eaca81c3ab Merge branch 'release' into dev 2025-12-19 10:53:18 -05:00
Jonathan Swoboda
93e38f2608 Merge pull request #12569 from esphome/bump-2025.12.1
2025.12.1
2025-12-19 10:53:05 -05:00
Jonathan Swoboda
3a888326d8 Bump version to 2025.12.1 2025-12-19 10:13:35 -05:00
Keith Burzinski
f0d0ea60a7 [esp32_ble, esp32_ble_tracker] Fix crash, error messages when ble.disable called during boot (#12560) 2025-12-19 10:13:35 -05:00
Jonathan Swoboda
7ca11764ab [template.alarm_control_panel] Fix compile without binary_sensor (#12548)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 10:13:35 -05:00
Jonathan Swoboda
3e38a5e630 [esp32_camera] Fix I2C driver conflict with other components (#12533)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-19 10:13:35 -05:00
Jonathan Swoboda
636be92c97 [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 10:13:35 -05:00
Jack Wilsdon
195b1c6323 [pm1006] Fix "never" update interval detection (#12529) 2025-12-19 10:13:35 -05:00
Anna Oake
7e08092012 [cc1101] Fix default frequencies (#12539) 2025-12-19 10:13:35 -05:00
pixelgrb
f962497db1 [mmc5603] enable AUTO_SR_en to compensate for temperature drift (#12556)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-18 23:13:36 -05:00
Keith Burzinski
7ae3a11d6b [esp32_ble, esp32_ble_tracker] Fix crash, error messages when ble.disable called during boot (#12560) 2025-12-18 19:42:47 -06:00
dependabot[bot]
1c50c2b672 Bump ruamel-yaml from 0.18.16 to 0.18.17 (#12555)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 11:19:19 -10:00
Jonathan Swoboda
41fd1762e9 [core] Deprecate custom_components folder (#12552)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 11:46:16 -05:00
J. Nick Koston
2cf6ed2af7 [socket] Refactor socket implementations for memory efficiency and code quality (#12550) 2025-12-18 09:07:35 -07:00
J. Nick Koston
b47b7d43fd [api] Remove unused force parameter from encode_message (#12551) 2025-12-18 09:06:16 -07:00
Jonathan Swoboda
663a4304e0 [libretiny] Fix millis() ambiguity on BK72XX (#12534)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 07:50:31 -05:00
Douwe
c3f86054e8 Merge branch 'dev' into wh_template 2025-12-18 08:31:43 +01:00
Jonathan Swoboda
ca47bad90a [template.alarm_control_panel] Fix compile without binary_sensor (#12548)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 23:34:04 -05:00
J. Nick Koston
4f821a6d76 [wifi] Reduce scan logging to prevent blocking loop during connection (#12544) 2025-12-17 18:21:46 -10:00
J. Nick Koston
426305836d [esp32][libretiny] Avoid duplicate snprintf when syncing preferences (#12542) 2025-12-17 18:16:14 -10:00
dependabot[bot]
1b5af7d21d Bump github/codeql-action from 4.31.8 to 4.31.9 (#12524)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 15:22:19 -10:00
David Woodhouse
9de7df7b5b Add build info to image (#12425)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-18 00:06:52 +00:00
Jonathan Swoboda
2b337aa306 [esp32_camera] Fix I2C driver conflict with other components (#12533)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:37:59 -05:00
Jonathan Swoboda
4ddaff4027 [esp32] Dynamically embed managed component server certificates (#12509)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:26:56 -05:00
J. Nick Koston
91c504061b [select] Eliminate string allocation in state callbacks (#12505)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 12:19:26 -10:00
Jonathan Swoboda
dc8f7abce2 [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 17:07:42 -05:00
Jonathan Swoboda
3d673ac55e [ci] Check changed headers in clang-tidy when using --changed (#12540)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 11:13:18 -10:00
Jack Wilsdon
b02696edc0 [pm1006] Fix "never" update interval detection (#12529) 2025-12-18 07:40:31 +11:00
Anna Oake
f9720026d0 [cc1101] Fix default frequencies (#12539) 2025-12-17 14:19:18 -05:00
Jonathan Swoboda
d7b04a3d18 [nextion] Fix clang-tidy error on Zephyr for HTTPClient (#12538)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 13:59:49 -05:00
Jonathan Swoboda
0e71fa97a7 [spi] Add SPIInterface stub for clang-tidy on unsupported platforms (#12532)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 12:18:25 -05:00
J. Nick Koston
42e061c9ae [text] Avoid string copies in callbacks by passing const ref (#12504) 2025-12-17 12:00:19 -05:00
J. Nick Koston
94763ebdab [libretiny] Store preference keys as uint32_t, convert to string only at FlashDB boundary (#12500) 2025-12-17 11:59:40 -05:00
J. Nick Koston
f32bb618ac [esp32] Store preference keys as uint32_t, convert to string only at NVS boundary (#12494) 2025-12-17 11:59:35 -05:00
Edward Firmo
0707f383a6 [nextion] Use ESP-IDF for ESP32 Arduino (#9429)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:17 -05:00
Piotr Szulc
e91c6a79ea [deep_sleep] Deep sleep for BK72xx (#12267)
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>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:05 -05:00
J. Nick Koston
63fc8b4e5a [core] Refactor str_snake_case and str_sanitize to use constexpr helpers (#12454) 2025-12-17 11:30:12 -05:00
J. Nick Koston
ab73ed76b8 [esphome] Improve OTA field alignment to save 4 bytes on 32-bit (#12461) 2025-12-17 11:29:58 -05:00
J. Nick Koston
bf6a03d1cf [factory_reset] Optimize memory by storing interval as uint16_t seconds (#12462) 2025-12-17 11:29:51 -05:00
J. Nick Koston
9928ab09cf [time] Convert to C++17 nested namespace syntax (#12463) 2025-12-17 11:29:43 -05:00
Thomas Rupprecht
56c1691d72 [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 22:52:28 -05:00
Roger Fachini
a065990ab9 [update] Add check action to trigger update checks (#12415) 2025-12-16 22:20:12 -05:00
Stuart Parmenter
084f517a20 [hub75] Add set_brightness action (#12521) 2025-12-16 22:12:33 -05:00
Jonathan Swoboda
1122ec354f [esp32] Add OTA rollback support (#12460)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:57 -05:00
Jonathan Swoboda
431183eebc [ledc,mqtt,resampler] Remove unnecessary ESP-IDF framework restrictions (#12442)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:09 -05:00
Jonathan Swoboda
08beaf8750 [esp32] Remove Arduino-specific code from core.cpp (#12501)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:06:12 -05:00
Jonathan Swoboda
18814f12dc [http_request] Use ESP-IDF for ESP32 Arduino (#12428)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:14 -05:00
Jonathan Swoboda
9cd888cef6 [spi] Use ESP-IDF driver for ESP32 Arduino (#12420)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:01 -05:00
Thomas Rupprecht
9727c7135c [openthread] channel range, fix typo, use C++17 nested namespace syntax (#12422) 2025-12-16 19:43:18 -05:00
Thomas Rupprecht
93621d85b0 [climate] Improve temperature unit regex (#12032) 2025-12-16 19:43:10 -05:00
Thomas Rupprecht
046ea922e8 [esp32] improve types and variable naming (#12423) 2025-12-16 19:42:52 -05:00
Jeff Zigler
fab4efb469 [esp32] Fix serial logging on h2, c2 & c61 (#12522)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 19:42:12 -05:00
Jonathan Swoboda
efc5672567 Merge branch 'release' into dev 2025-12-16 18:57:37 -05:00
Jonathan Swoboda
0ea5f2fd81 Merge pull request #12525 from esphome/bump-2025.12.0
2025.12.0
2025-12-16 18:57:20 -05:00
Jonathan Swoboda
fa3d998c3d Bump version to 2025.12.0 2025-12-16 17:15:50 -05:00
Jonathan Swoboda
5e630e9255 Merge branch 'beta' into dev 2025-12-16 11:26:08 -05:00
Jonathan Swoboda
864aaeec01 Merge pull request #12520 from esphome/bump-2025.12.0b5
2025.12.0b5
2025-12-16 11:25:57 -05:00
Jonathan Swoboda
9c88e44300 Bump version to 2025.12.0b5 2025-12-16 10:35:31 -05:00
Jonathan Swoboda
4d6a93f92d [uart] Fix UART on default UART0 pins for ESP-IDF (#12519)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:35:31 -05:00
J. Nick Koston
7216120bfd [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) 2025-12-16 10:35:31 -05:00
Jonathan Swoboda
1897551b28 [uart] Fix UART on default UART0 pins for ESP-IDF (#12519)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:17:17 -05:00
dhoeben
ad3d7900f3 Add water_heater template 2025-12-16 09:27:25 +01:00
J. Nick Koston
ead60bc5c4 [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) 2025-12-16 00:48:30 -06:00
Jonathan Swoboda
7fe8e53f82 Merge branch 'beta' into dev 2025-12-15 19:01:12 -05:00
Jonathan Swoboda
8cf0ee38a3 Merge pull request #12513 from esphome/bump-2025.12.0b4
2025.12.0b4
2025-12-15 19:01:02 -05:00
Jonathan Swoboda
4c926cca60 Bump version to 2025.12.0b4 2025-12-15 18:09:42 -05:00
Pascal Vizeli
57634b612a [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 18:09:42 -05:00
Jonathan Swoboda
8dff7ee746 [esp32] Support all IDF component version operators in shorthand syntax (#12499)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 18:09:42 -05:00
Jonathan Swoboda
803bb742c9 [remote_base] Fix crash when ABBWelcome action has no data field (#12493)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 18:09:42 -05:00
David Woodhouse
839139df36 Add FNV-1a hash functions (#12502) 2025-12-15 20:23:54 +00:00
dependabot[bot]
24d7e9dd23 Bump tornado from 6.5.3 to 6.5.4 (#12508)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:08:16 +00:00
dependabot[bot]
1214bb6bad Bump aioesphomeapi from 43.2.1 to 43.3.0 (#12507)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:07:20 +00:00
Pascal Vizeli
260ffba2a5 [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 12:54:12 -05:00
Jonathan Swoboda
2e899dd010 [esp32] Support all IDF component version operators in shorthand syntax (#12499)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 12:07:02 -05:00
David Woodhouse
61cbd07e1d Add hmac-sha256 support (#12437)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-15 10:55:03 -06:00
Jonathan Swoboda
450962850a [remote_base] Fix crash when ABBWelcome action has no data field (#12493)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 09:29:51 -05:00
Jonathan Swoboda
6b088caf5d Merge branch 'beta' into dev 2025-12-14 19:18:10 -05:00
Jonathan Swoboda
3e6a65e7dc Merge pull request #12488 from esphome/bump-2025.12.0b3
2025.12.0b3
2025-12-14 19:17:58 -05:00
Jonathan Swoboda
3a101d8886 Bump version to 2025.12.0b3 2025-12-14 18:17:00 -05:00
J. Nick Koston
fa0f07bfe9 [wifi] Fix WiFi recovery after failed connection attempts (#12483) 2025-12-14 18:17:00 -05:00
mbohdal
fffa16e4d8 [ethernet] fix used pins validation in configuration of RMII pins (#12486) 2025-12-14 18:17:00 -05:00
guillempages
734710d22a [core] Use Arduino string macros only on ESP8266 (#12471) 2025-12-14 18:17:00 -05:00
J. Nick Koston
3a1be6822e [ota] Match client timeout to device timeout to prevent premature failures (#12484) 2025-12-14 18:17:00 -05:00
J. Nick Koston
c85b1b8609 [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) 2025-12-14 18:17:00 -05:00
J. Nick Koston
2e9ddd967c [wifi_signal] Skip publishing disconnected RSSI value (#12482) 2025-12-14 18:17:00 -05:00
J. Nick Koston
078afe9656 [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) 2025-12-14 18:17:00 -05:00
Jonathan Swoboda
46574fcbec [cc1101] Add packet mode support (#12474)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 18:17:00 -05:00
Jonathan Swoboda
359f45400f [core] Fix polling_component_schema and type consistency (#12478)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 18:16:59 -05:00
Clyde Stubbs
4da95ccd7e [packet_transport] Ensure retransmission at update intervals (#12472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-14 18:16:59 -05:00
J. Nick Koston
c69d58273a [core] Fix CORE.raw_config not updated after package merge (#12456) 2025-12-14 18:16:59 -05:00
J. Nick Koston
ffce80f96c [wifi] Fix WiFi recovery after failed connection attempts (#12483) 2025-12-14 16:26:34 -06:00
mbohdal
fa5b14fad4 [ethernet] fix used pins validation in configuration of RMII pins (#12486) 2025-12-14 16:40:08 -05:00
guillempages
cee532a1e3 [core] Use Arduino string macros only on ESP8266 (#12471) 2025-12-15 07:15:19 +11:00
J. Nick Koston
8524b894d6 [ota] Match client timeout to device timeout to prevent premature failures (#12484) 2025-12-14 13:47:11 -06:00
J. Nick Koston
3a5e708c13 [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) 2025-12-14 13:31:19 -06:00
J. Nick Koston
96e418a8ca [wifi_signal] Skip publishing disconnected RSSI value (#12482) 2025-12-14 13:31:07 -06:00
J. Nick Koston
780a407b10 [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) 2025-12-14 13:30:55 -06:00
Jonathan Swoboda
cfc0d8bdfc [cc1101] Add packet mode support (#12474)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 13:22:55 -05:00
Jonathan Swoboda
786d7266f5 [core] Fix polling_component_schema and type consistency (#12478)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 12:47:52 -05:00
Clyde Stubbs
ede64a9f47 [packet_transport] Ensure retransmission at update intervals (#12472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-14 12:47:15 -05:00
J. Nick Koston
e0ce66e011 [core] Fix CORE.raw_config not updated after package merge (#12456) 2025-12-13 07:38:31 -06:00
David Woodhouse
6fce0a6104 Add host platform support to MD5 component (#12458) 2025-12-13 02:50:34 +00:00
595 changed files with 17790 additions and 9186 deletions

View File

@@ -1 +1 @@
766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07 94557f94be073390342833aff12ef8676a8b597db5fa770a5a1232e9425cb48f

View File

@@ -49,7 +49,7 @@ jobs:
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@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Set TAG - name: Set TAG
run: | run: |

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -99,7 +99,7 @@ jobs:
python-version: "3.11" python-version: "3.11"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
@@ -178,7 +178,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'

4
.gitignore vendored
View File

@@ -91,6 +91,10 @@ venv-*/
# mypy # mypy
.mypy_cache/ .mypy_cache/
# nix
/default.nix
/shell.nix
.pioenvs .pioenvs
.piolibdeps .piolibdeps
.pio .pio

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.14.9 rev: v0.14.10
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@@ -42,6 +42,7 @@ esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah esphome/components/apds9306/* @aodrenah
esphome/components/api/* @esphome/core esphome/components/api/* @esphome/core
esphome/components/aqi/* @freekode @jasstrong @ximex
esphome/components/as5600/* @ammmze esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze esphome/components/as5600/sensor/* @ammmze
esphome/components/as7341/* @mrgnr esphome/components/as7341/* @mrgnr
@@ -90,6 +91,7 @@ esphome/components/bmp3xx_spi/* @latonita
esphome/components/bmp581/* @kahrendt esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/bthome_mithermometer/* @nagyrobi
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @bdraco @DT-art1 esphome/components/camera/* @bdraco @DT-art1
@@ -215,6 +217,7 @@ esphome/components/hlk_fm22x/* @OnFreund
esphome/components/hlw8032/* @rici4kubicek esphome/components/hlw8032/* @rici4kubicek
esphome/components/hm3301/* @freekode esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2 esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter 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
@@ -517,6 +520,7 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb esphome/components/uart/button/* @ssieb
esphome/components/uart/event/* @eoasmxd
esphome/components/uart/packet_transport/* @clydebarrow esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ec/* @pvizeli
@@ -535,6 +539,7 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz @kahrendt esphome/components/voice_assistant/* @jesserockz @kahrendt
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher esphome/components/watchdog/* @oarcher
esphome/components/water_heater/* @dhoeben
esphome/components/waveshare_epaper/* @clydebarrow esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server/ota/* @esphome/core esphome/components/web_server/ota/* @esphome/core
esphome/components/web_server_base/* @esphome/core esphome/components/web_server_base/* @esphome/core

View File

@@ -1,6 +1,7 @@
include LICENSE include LICENSE
include README.md include README.md
include requirements.txt include requirements.txt
recursive-include esphome *.yaml
recursive-include esphome *.cpp *.h *.tcc *.c recursive-include esphome *.cpp *.h *.tcc *.c
recursive-include esphome *.py.script recursive-include esphome *.py.script
recursive-include esphome LICENSE.txt recursive-include esphome LICENSE.txt

View File

@@ -11,6 +11,16 @@ FROM base-source-${BUILD_TYPE} AS base
RUN git config --system --add safe.directory "*" RUN git config --system --add safe.directory "*"
# Install build tools for Python packages that require compilation
# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager)
RUN if command -v apk > /dev/null; then \
apk add --no-cache build-base; \
else \
apt-get update \
&& apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*; \
fi
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 ENV PIP_DISABLE_PIP_VERSION_CHECK=1
RUN pip install --no-cache-dir -U pip uv==0.6.14 RUN pip install --no-cache-dir -U pip uv==0.6.14

View File

@@ -518,10 +518,49 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
rc = platformio_api.run_compile(config, CORE.verbose) rc = platformio_api.run_compile(config, CORE.verbose)
if rc != 0: if rc != 0:
return rc return rc
# Check if firmware was rebuilt and emit build_info + create manifest
_check_and_emit_build_info()
idedata = platformio_api.get_idedata(config) idedata = platformio_api.get_idedata(config)
return 0 if idedata is not None else 1 return 0 if idedata is not None else 1
def _check_and_emit_build_info() -> None:
"""Check if firmware was rebuilt and emit build_info."""
import json
firmware_path = CORE.firmware_bin
build_info_json_path = CORE.relative_build_path("build_info.json")
# Check if both files exist
if not firmware_path.exists() or not build_info_json_path.exists():
return
# Check if firmware is newer than build_info (indicating a relink occurred)
if firmware_path.stat().st_mtime <= build_info_json_path.stat().st_mtime:
return
# Read build_info from JSON
try:
with open(build_info_json_path, encoding="utf-8") as f:
build_info = json.load(f)
except (OSError, json.JSONDecodeError) as e:
_LOGGER.debug("Failed to read build_info: %s", e)
return
config_hash = build_info.get("config_hash")
build_time_str = build_info.get("build_time_str")
if config_hash is None or build_time_str is None:
return
# Emit build_info with human-readable time
_LOGGER.info(
"Build Info: config_hash=0x%08x build_time_str=%s", config_hash, build_time_str
)
def upload_using_esptool( def upload_using_esptool(
config: ConfigType, port: str, file: str, speed: int config: ConfigType, port: str, file: str, speed: int
) -> str | int: ) -> str | int:
@@ -750,7 +789,13 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
exit_code = compile_program(args, config) exit_code = compile_program(args, config)
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info("Successfully compiled program.") if CORE.is_host:
from esphome.platformio_api import get_idedata
program_path = str(get_idedata(config).firmware_elf_path)
_LOGGER.info("Successfully compiled program to path '%s'", program_path)
else:
_LOGGER.info("Successfully compiled program.")
return 0 return 0
@@ -800,10 +845,8 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
if CORE.is_host: if CORE.is_host:
from esphome.platformio_api import get_idedata from esphome.platformio_api import get_idedata
idedata = get_idedata(config) program_path = str(get_idedata(config).firmware_elf_path)
if idedata is None: _LOGGER.info("Running program from path '%s'", program_path)
return 1
program_path = idedata.raw["prog_path"]
return run_external_process(program_path) return run_external_process(program_path)
# Get devices, resolving special identifiers like OTA # Get devices, resolving special identifiers like OTA

View File

@@ -30,7 +30,9 @@ void A01nyubComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters); ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters); this->publish_state(meters);
} else { } else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
} }
} else { } else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);

View File

@@ -29,7 +29,9 @@ void A02yyuwComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %f mm", distance); ESP_LOGV(TAG, "Distance from sensor: %f mm", distance);
this->publish_state(distance); this->publish_state(distance);
} else { } else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
} }
} else { } else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);

View File

@@ -25,11 +25,13 @@ class AddressableLightDisplay : public display::DisplayBuffer {
if (enabled_ && !enabled) { // enabled -> disabled if (enabled_ && !enabled) { // enabled -> disabled
// - Tell the parent light to refresh, effectively wiping the display. Also // - Tell the parent light to refresh, effectively wiping the display. Also
// restores the previous effect (if any). // restores the previous effect (if any).
light_state_->make_call().set_effect(this->last_effect_).perform(); if (this->last_effect_index_.has_value()) {
light_state_->make_call().set_effect(*this->last_effect_index_).perform();
}
} else if (!enabled_ && enabled) { // disabled -> enabled } else if (!enabled_ && enabled) { // disabled -> enabled
// - Save the current effect. // - Save the current effect index.
this->last_effect_ = light_state_->get_effect_name(); this->last_effect_index_ = light_state_->get_current_effect_index();
// - Disable any current effect. // - Disable any current effect.
light_state_->make_call().set_effect(0).perform(); light_state_->make_call().set_effect(0).perform();
} }
@@ -56,7 +58,7 @@ class AddressableLightDisplay : public display::DisplayBuffer {
int32_t width_; int32_t width_;
int32_t height_; int32_t height_;
std::vector<Color> addressable_light_buffer_; std::vector<Color> addressable_light_buffer_;
optional<std::string> last_effect_; optional<uint32_t> last_effect_index_;
optional<std::function<int(int, int)>> pixel_mapper_f_; optional<std::function<int(int, int)>> pixel_mapper_f_;
}; };
} // namespace addressable_light } // namespace addressable_light

View File

@@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All(
{ {
cv.GenerateID(): cv.declare_id(ADE7880), cv.GenerateID(): cv.declare_id(ADE7880),
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All( cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
cv.frequency, cv.Range(min=45.0, max=66.0) cv.frequency, cv.float_range(min=45.0, max=66.0)
), ),
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema, cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,

View File

@@ -8,8 +8,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
static const char *const TAG = "alarm_control_panel"; static const char *const TAG = "alarm_control_panel";
@@ -35,26 +34,12 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)), ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state; this->current_state_ = state;
// Single state callback - triggers check get_state() for specific states
this->state_callback_.call(); this->state_callback_.call();
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) #if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_alarm_control_panel_update(this); ControllerRegistry::notify_alarm_control_panel_update(this);
#endif #endif
if (state == ACP_STATE_TRIGGERED) { // Cleared fires when leaving TRIGGERED state
this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {
this->arming_callback_.call();
} else if (state == ACP_STATE_PENDING) {
this->pending_callback_.call();
} else if (state == ACP_STATE_ARMED_HOME) {
this->armed_home_callback_.call();
} else if (state == ACP_STATE_ARMED_NIGHT) {
this->armed_night_callback_.call();
} else if (state == ACP_STATE_ARMED_AWAY) {
this->armed_away_callback_.call();
} else if (state == ACP_STATE_DISARMED) {
this->disarmed_callback_.call();
}
if (prev_state == ACP_STATE_TRIGGERED) { if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call(); this->cleared_callback_.call();
} }
@@ -69,34 +54,6 @@ void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback)
this->state_callback_.add(std::move(callback)); this->state_callback_.add(std::move(callback));
} }
void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
this->triggered_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_arming_callback(std::function<void()> &&callback) {
this->arming_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_home_callback(std::function<void()> &&callback) {
this->armed_home_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_night_callback(std::function<void()> &&callback) {
this->armed_night_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_away_callback(std::function<void()> &&callback) {
this->armed_away_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_pending_callback(std::function<void()> &&callback) {
this->pending_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_disarmed_callback(std::function<void()> &&callback) {
this->disarmed_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) { void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback)); this->cleared_callback_.add(std::move(callback));
} }
@@ -157,5 +114,4 @@ void AlarmControlPanel::disarm(optional<std::string> code) {
call.perform(); call.perform();
} }
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
#include <map>
#include "alarm_control_panel_call.h" #include "alarm_control_panel_call.h"
#include "alarm_control_panel_state.h" #include "alarm_control_panel_state.h"
@@ -9,8 +7,7 @@
#include "esphome/core/entity_base.h" #include "esphome/core/entity_base.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
enum AlarmControlPanelFeature : uint8_t { enum AlarmControlPanelFeature : uint8_t {
// Matches Home Assistant values // Matches Home Assistant values
@@ -35,54 +32,13 @@ class AlarmControlPanel : public EntityBase {
*/ */
void publish_state(AlarmControlPanelState state); void publish_state(AlarmControlPanelState state);
/** Add a callback for when the state of the alarm_control_panel changes /** Add a callback for when the state of the alarm_control_panel changes.
* Triggers can check get_state() to determine the new state.
* *
* @param callback The callback function * @param callback The callback function
*/ */
void add_on_state_callback(std::function<void()> &&callback); void add_on_state_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to triggered
*
* @param callback The callback function
*/
void add_on_triggered_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to arming
*
* @param callback The callback function
*/
void add_on_arming_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to pending
*
* @param callback The callback function
*/
void add_on_pending_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_home
*
* @param callback The callback function
*/
void add_on_armed_home_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_night
*
* @param callback The callback function
*/
void add_on_armed_night_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_away
*
* @param callback The callback function
*/
void add_on_armed_away_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to disarmed
*
* @param callback The callback function
*/
void add_on_disarmed_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered /** Add a callback for when the state of the alarm_control_panel clears from triggered
* *
* @param callback The callback function * @param callback The callback function
@@ -172,29 +128,14 @@ class AlarmControlPanel : public EntityBase {
uint32_t last_update_; uint32_t last_update_;
// the call control function // the call control function
virtual void control(const AlarmControlPanelCall &call) = 0; virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback // state callback - triggers check get_state() for specific state
CallbackManager<void()> state_callback_{}; LazyCallbackManager<void()> state_callback_{};
// trigger callback // clear callback - fires when leaving TRIGGERED state
CallbackManager<void()> triggered_callback_{}; LazyCallbackManager<void()> cleared_callback_{};
// arming callback
CallbackManager<void()> arming_callback_{};
// pending callback
CallbackManager<void()> pending_callback_{};
// armed_home callback
CallbackManager<void()> armed_home_callback_{};
// armed_night callback
CallbackManager<void()> armed_night_callback_{};
// armed_away callback
CallbackManager<void()> armed_away_callback_{};
// disarmed callback
CallbackManager<void()> disarmed_callback_{};
// clear callback
CallbackManager<void()> cleared_callback_{};
// chime callback // chime callback
CallbackManager<void()> chime_callback_{}; LazyCallbackManager<void()> chime_callback_{};
// ready callback // ready callback
CallbackManager<void()> ready_callback_{}; LazyCallbackManager<void()> ready_callback_{};
}; };
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -4,8 +4,7 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
static const char *const TAG = "alarm_control_panel"; static const char *const TAG = "alarm_control_panel";
@@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() {
} }
} }
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -6,8 +6,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
class AlarmControlPanel; class AlarmControlPanel;
@@ -36,5 +35,4 @@ class AlarmControlPanelCall {
void validate_(); void validate_();
}; };
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -1,7 +1,6 @@
#include "alarm_control_panel_state.h" #include "alarm_control_panel_state.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) { const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
switch (state) { switch (state) {
@@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
} }
} }
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -3,8 +3,7 @@
#include <cstdint> #include <cstdint>
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
enum AlarmControlPanelState : uint8_t { enum AlarmControlPanelState : uint8_t {
ACP_STATE_DISARMED = 0, ACP_STATE_DISARMED = 0,
@@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t {
*/ */
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state); const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -3,9 +3,9 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "alarm_control_panel.h" #include "alarm_control_panel.h"
namespace esphome { namespace esphome::alarm_control_panel {
namespace alarm_control_panel {
/// Trigger on any state change
class StateTrigger : public Trigger<> { class StateTrigger : public Trigger<> {
public: public:
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) { explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -13,55 +13,30 @@ class StateTrigger : public Trigger<> {
} }
}; };
class TriggeredTrigger : public Trigger<> { /// Template trigger that fires when entering a specific state
template<AlarmControlPanelState State> class StateEnterTrigger : public Trigger<> {
public: public:
explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) { explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {
alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); }); alarm_control_panel->add_on_state_callback([this]() {
if (this->alarm_control_panel_->get_state() == State)
this->trigger();
});
} }
protected:
AlarmControlPanel *alarm_control_panel_;
}; };
class ArmingTrigger : public Trigger<> { // Type aliases for state-specific triggers
public: using TriggeredTrigger = StateEnterTrigger<ACP_STATE_TRIGGERED>;
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) { using ArmingTrigger = StateEnterTrigger<ACP_STATE_ARMING>;
alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); }); using PendingTrigger = StateEnterTrigger<ACP_STATE_PENDING>;
} using ArmedHomeTrigger = StateEnterTrigger<ACP_STATE_ARMED_HOME>;
}; using ArmedNightTrigger = StateEnterTrigger<ACP_STATE_ARMED_NIGHT>;
using ArmedAwayTrigger = StateEnterTrigger<ACP_STATE_ARMED_AWAY>;
class PendingTrigger : public Trigger<> { using DisarmedTrigger = StateEnterTrigger<ACP_STATE_DISARMED>;
public:
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); });
}
};
class ArmedHomeTrigger : public Trigger<> {
public:
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); });
}
};
class ArmedNightTrigger : public Trigger<> {
public:
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); });
}
};
class ArmedAwayTrigger : public Trigger<> {
public:
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); });
}
};
class DisarmedTrigger : public Trigger<> {
public:
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); });
}
};
/// Trigger when leaving TRIGGERED state (alarm cleared)
class ClearedTrigger : public Trigger<> { class ClearedTrigger : public Trigger<> {
public: public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -69,6 +44,7 @@ class ClearedTrigger : public Trigger<> {
} }
}; };
/// Trigger on chime event (zone opened while disarmed)
class ChimeTrigger : public Trigger<> { class ChimeTrigger : public Trigger<> {
public: public:
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -76,6 +52,7 @@ class ChimeTrigger : public Trigger<> {
} }
}; };
/// Trigger on ready state change
class ReadyTrigger : public Trigger<> { class ReadyTrigger : public Trigger<> {
public: public:
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -187,5 +164,4 @@ template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts.
AlarmControlPanel *parent_; AlarmControlPanel *parent_;
}; };
} // namespace alarm_control_panel } // namespace esphome::alarm_control_panel
} // namespace esphome

View File

@@ -226,32 +226,6 @@ 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/#configuration-variables"
)
return config
def _consume_api_sockets(config: ConfigType) -> ConfigType: def _consume_api_sockets(config: ConfigType) -> ConfigType:
"""Register socket needs for API component.""" """Register socket needs for API component."""
from esphome.components import socket from esphome.components import socket
@@ -268,7 +242,17 @@ CONFIG_SCHEMA = cv.All(
{ {
cv.GenerateID(): cv.declare_id(APIServer), cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port, cv.Optional(CONF_PORT, default=6053): cv.port,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, # Removed in 2026.1.0 - kept to provide helpful error message
cv.Optional(CONF_PASSWORD): cv.invalid(
"The 'password' option has been removed in ESPHome 2026.1.0.\n"
"Password authentication was deprecated in May 2022.\n"
"Please migrate to encryption for secure API communication:\n\n"
"api:\n"
" encryption:\n"
" key: !secret api_encryption_key\n\n"
"Generate a key with: openssl rand -base64 32\n"
"Or visit https://esphome.io/components/api/#configuration-variables"
),
cv.Optional( cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min" CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
@@ -330,7 +314,6 @@ CONFIG_SCHEMA = cv.All(
} }
).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,
_consume_api_sockets, _consume_api_sockets,
) )
@@ -344,9 +327,6 @@ async def to_code(config: ConfigType) -> None:
CORE.register_controller() CORE.register_controller()
cg.add(var.set_port(config[CONF_PORT])) cg.add(var.set_port(config[CONF_PORT]))
if config[CONF_PASSWORD]:
cg.add_define("USE_API_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: if CONF_LISTEN_BACKLOG in config:

View File

@@ -7,10 +7,7 @@ service APIConnection {
option (needs_setup_connection) = false; option (needs_setup_connection) = false;
option (needs_authentication) = false; option (needs_authentication) = false;
} }
rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) { // REMOVED in ESPHome 2026.1.0: rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse)
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) { rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
option (needs_setup_connection) = false; option (needs_setup_connection) = false;
option (needs_authentication) = false; option (needs_authentication) = false;
@@ -82,14 +79,13 @@ service APIConnection {
// * VarInt denoting the type of message. // * VarInt denoting the type of message.
// * The message object encoded as a ProtoBuf message // * The message object encoded as a ProtoBuf message
// The connection is established in 4 steps: // The connection is established in 2 steps:
// * First, the client connects to the server and sends a "Hello Request" identifying itself // * First, the client connects to the server and sends a "Hello Request" identifying itself
// * The server responds with a "Hello Response" and selects the protocol version // * The server responds with a "Hello Response" and the connection is authenticated
// * After receiving this message, the client attempts to authenticate itself using
// the password and a "Connect Request"
// * The server responds with a "Connect Response" and notifies of invalid password.
// If anything in this initial process fails, the connection must immediately closed // If anything in this initial process fails, the connection must immediately closed
// by both sides and _no_ disconnection message is to be sent. // by both sides and _no_ disconnection message is to be sent.
// Note: Password authentication via AuthenticationRequest/AuthenticationResponse (message IDs 3, 4)
// was removed in ESPHome 2026.1.0. Those message IDs are reserved and should not be reused.
// Message sent at the beginning of each connection // Message sent at the beginning of each connection
// 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
@@ -102,7 +98,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 [(pointer_to_buffer) = true]; string client_info = 1;
uint32 api_version_major = 2; uint32 api_version_major = 2;
uint32 api_version_minor = 3; uint32 api_version_minor = 3;
} }
@@ -130,25 +126,23 @@ message HelloResponse {
string name = 4; string name = 4;
} }
// Message sent at the beginning of each connection to authenticate the client // DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported.
// Can only be sent by the client and only at the beginning of the connection // These messages are kept for protocol documentation but are not processed by the server.
// Use noise encryption instead: https://esphome.io/components/api/#configuration-variables
message AuthenticationRequest { 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"; option deprecated = true;
// 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.
// Can only be sent by the server and only at the beginning of the connection
message AuthenticationResponse { 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"; option deprecated = true;
bool invalid_password = 1; bool invalid_password = 1;
} }
@@ -205,7 +199,9 @@ message DeviceInfoResponse {
option (id) = 10; option (id) = 10;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"]; // Deprecated in ESPHome 2026.1.0, but kept for backward compatibility
// with older ESPHome versions that still send this field.
bool uses_password = 1 [deprecated = true];
// The name of the node, given by "App.set_name()" // The name of the node, given by "App.set_name()"
string name = 2; string name = 2;
@@ -579,7 +575,7 @@ message LightCommandRequest {
bool has_flash_length = 16; bool has_flash_length = 16;
uint32 flash_length = 17; uint32 flash_length = 17;
bool has_effect = 18; bool has_effect = 18;
string effect = 19 [(pointer_to_buffer) = true]; string effect = 19;
uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"];
} }
@@ -796,7 +792,7 @@ message HomeassistantActionResponse {
uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
bool success = 2; // Whether the service call succeeded bool success = 2; // Whether the service call succeeded
string error_message = 3; // Error message if success = false string error_message = 3; // Error message if success = false
bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
} }
// ==================== IMPORT HOME ASSISTANT STATES ==================== // ==================== IMPORT HOME ASSISTANT STATES ====================
@@ -841,7 +837,7 @@ message GetTimeResponse {
option (no_delay) = true; option (no_delay) = true;
fixed32 epoch_seconds = 1; fixed32 epoch_seconds = 1;
string timezone = 2 [(pointer_to_buffer) = true]; string timezone = 2;
} }
// ==================== USER-DEFINES SERVICES ==================== // ==================== USER-DEFINES SERVICES ====================
@@ -1101,6 +1097,85 @@ message ClimateCommandRequest {
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
} }
// ==================== WATER_HEATER ====================
enum WaterHeaterMode {
WATER_HEATER_MODE_OFF = 0;
WATER_HEATER_MODE_ECO = 1;
WATER_HEATER_MODE_ELECTRIC = 2;
WATER_HEATER_MODE_PERFORMANCE = 3;
WATER_HEATER_MODE_HIGH_DEMAND = 4;
WATER_HEATER_MODE_HEAT_PUMP = 5;
WATER_HEATER_MODE_GAS = 6;
}
message ListEntitiesWaterHeaterResponse {
option (id) = 132;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_WATER_HEATER";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 5;
EntityCategory entity_category = 6;
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
float min_temperature = 8;
float max_temperature = 9;
float target_temperature_step = 10;
repeated WaterHeaterMode supported_modes = 11 [(container_pointer_no_template) = "water_heater::WaterHeaterModeMask"];
// Bitmask of WaterHeaterFeature flags
uint32 supported_features = 12;
}
message WaterHeaterStateResponse {
option (id) = 133;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_WATER_HEATER";
option (no_delay) = true;
fixed32 key = 1;
float current_temperature = 2;
float target_temperature = 3;
WaterHeaterMode mode = 4;
uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"];
// Bitmask of current state flags (bit 0 = away, bit 1 = on)
uint32 state = 6;
float target_temperature_low = 7;
float target_temperature_high = 8;
}
// Bitmask for WaterHeaterCommandRequest.has_fields
enum WaterHeaterCommandHasField {
WATER_HEATER_COMMAND_HAS_NONE = 0;
WATER_HEATER_COMMAND_HAS_MODE = 1;
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2;
WATER_HEATER_COMMAND_HAS_STATE = 4;
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8;
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16;
}
message WaterHeaterCommandRequest {
option (id) = 134;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_WATER_HEATER";
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
// Bitmask of which fields are set (see WaterHeaterCommandHasField)
uint32 has_fields = 2;
WaterHeaterMode mode = 3;
float target_temperature = 4;
uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"];
// State flags bitmask (bit 0 = away, bit 1 = on)
uint32 state = 6;
float target_temperature_low = 7;
float target_temperature_high = 8;
}
// ==================== NUMBER ==================== // ==================== NUMBER ====================
enum NumberMode { enum NumberMode {
NUMBER_MODE_AUTO = 0; NUMBER_MODE_AUTO = 0;
@@ -1195,7 +1270,7 @@ message SelectCommandRequest {
option (base_class) = "CommandProtoMessage"; option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
string state = 2 [(pointer_to_buffer) = true]; string state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
} }
@@ -1213,7 +1288,7 @@ message ListEntitiesSirenResponse {
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 6; bool disabled_by_default = 6;
repeated string tones = 7; repeated string tones = 7 [(container_pointer_no_template) = "FixedVector<const char *>"];
bool supports_duration = 8; bool supports_duration = 8;
bool supports_volume = 9; bool supports_volume = 9;
EntityCategory entity_category = 10; EntityCategory entity_category = 10;
@@ -1613,7 +1688,7 @@ message BluetoothGATTWriteRequest {
uint32 handle = 2; uint32 handle = 2;
bool response = 3; bool response = 3;
bytes data = 4 [(pointer_to_buffer) = true]; bytes data = 4;
} }
message BluetoothGATTReadDescriptorRequest { message BluetoothGATTReadDescriptorRequest {
@@ -1633,7 +1708,7 @@ message BluetoothGATTWriteDescriptorRequest {
uint64 address = 1; uint64 address = 1;
uint32 handle = 2; uint32 handle = 2;
bytes data = 3 [(pointer_to_buffer) = true]; bytes data = 3;
} }
message BluetoothGATTNotifyRequest { message BluetoothGATTNotifyRequest {
@@ -1858,7 +1933,7 @@ message VoiceAssistantAudio {
option (source) = SOURCE_BOTH; option (source) = SOURCE_BOTH;
option (ifdef) = "USE_VOICE_ASSISTANT"; option (ifdef) = "USE_VOICE_ASSISTANT";
bytes data = 1; bytes data = 1 [(pointer_to_buffer) = true];
bool end = 2; bool end = 2;
} }
@@ -2346,7 +2421,7 @@ message ZWaveProxyFrame {
option (ifdef) = "USE_ZWAVE_PROXY"; option (ifdef) = "USE_ZWAVE_PROXY";
option (no_delay) = true; option (no_delay) = true;
bytes data = 1 [(pointer_to_buffer) = true]; bytes data = 1;
} }
enum ZWaveProxyRequestType { enum ZWaveProxyRequestType {
@@ -2360,5 +2435,5 @@ message ZWaveProxyRequest {
option (ifdef) = "USE_ZWAVE_PROXY"; option (ifdef) = "USE_ZWAVE_PROXY";
ZWaveProxyRequestType type = 1; ZWaveProxyRequestType type = 1;
bytes data = 2 [(pointer_to_buffer) = true]; bytes data = 2;
} }

View File

@@ -13,6 +13,7 @@
#include <cinttypes> #include <cinttypes>
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <new>
#include <utility> #include <utility>
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#include <pgmspace.h> #include <pgmspace.h>
@@ -42,6 +43,9 @@
#ifdef USE_ZWAVE_PROXY #ifdef USE_ZWAVE_PROXY
#include "esphome/components/zwave_proxy/zwave_proxy.h" #include "esphome/components/zwave_proxy/zwave_proxy.h"
#endif #endif
#ifdef USE_WATER_HEATER
#include "esphome/components/water_heater/water_heater.h"
#endif
namespace esphome::api { namespace esphome::api {
@@ -93,8 +97,7 @@ static const int CAMERA_STOP_STREAM = 5000;
return; return;
#endif // USE_DEVICES #endif // USE_DEVICES
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto &noise_ctx = parent->get_noise_ctx(); auto &noise_ctx = parent->get_noise_ctx();
if (noise_ctx.has_psk()) { if (noise_ctx.has_psk()) {
@@ -133,6 +136,7 @@ void APIConnection::start() {
} }
APIConnection::~APIConnection() { APIConnection::~APIConnection() {
this->destroy_active_iterator_();
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) { if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
@@ -145,6 +149,32 @@ APIConnection::~APIConnection() {
#endif #endif
} }
void APIConnection::destroy_active_iterator_() {
switch (this->active_iterator_) {
case ActiveIterator::LIST_ENTITIES:
this->iterator_storage_.list_entities.~ListEntitiesIterator();
break;
case ActiveIterator::INITIAL_STATE:
this->iterator_storage_.initial_state.~InitialStateIterator();
break;
case ActiveIterator::NONE:
break;
}
this->active_iterator_ = ActiveIterator::NONE;
}
void APIConnection::begin_iterator_(ActiveIterator type) {
this->destroy_active_iterator_();
this->active_iterator_ = type;
if (type == ActiveIterator::LIST_ENTITIES) {
new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
this->iterator_storage_.list_entities.begin();
} else {
new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
this->iterator_storage_.initial_state.begin();
}
}
void APIConnection::loop() { void APIConnection::loop() {
if (this->flags_.next_close) { if (this->flags_.next_close) {
// requested a disconnect // requested a disconnect
@@ -187,23 +217,35 @@ void APIConnection::loop() {
this->process_batch_(); this->process_batch_();
} }
if (!this->list_entities_iterator_.completed()) { switch (this->active_iterator_) {
this->process_iterator_batch_(this->list_entities_iterator_); case ActiveIterator::LIST_ENTITIES:
} else if (!this->initial_state_iterator_.completed()) { if (this->iterator_storage_.list_entities.completed()) {
this->process_iterator_batch_(this->initial_state_iterator_); this->destroy_active_iterator_();
if (this->flags_.state_subscription) {
// If we've completed initial states, process any remaining and clear the flag this->begin_iterator_(ActiveIterator::INITIAL_STATE);
if (this->initial_state_iterator_.completed()) { }
// Process any remaining batched messages immediately } else {
if (!this->deferred_batch_.empty()) { this->process_iterator_batch_(this->iterator_storage_.list_entities);
this->process_batch_();
} }
// Now that everything is sent, enable immediate sending for future state changes break;
this->flags_.should_try_send_immediately = true; case ActiveIterator::INITIAL_STATE:
// Release excess memory from buffers that grew during initial sync if (this->iterator_storage_.initial_state.completed()) {
this->deferred_batch_.release_buffer(); this->destroy_active_iterator_();
this->helper_->release_buffers(); // Process any remaining batched messages immediately
} if (!this->deferred_batch_.empty()) {
this->process_batch_();
}
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
// Release excess memory from buffers that grew during initial sync
this->deferred_batch_.release_buffer();
this->helper_->release_buffers();
} else {
this->process_iterator_batch_(this->iterator_storage_.initial_state);
}
break;
case ActiveIterator::NONE:
break;
} }
if (this->flags_.sent_ping) { if (this->flags_.sent_ping) {
@@ -228,33 +270,17 @@ void APIConnection::loop() {
} }
} }
#ifdef USE_CAMERA
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_->available() == to_send;
CameraImageResponse msg;
msg.key = camera::Camera::instance()->get_object_id_hash();
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
msg.done = done;
#ifdef USE_DEVICES
msg.device_id = camera::Camera::instance()->get_device_id();
#endif
if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
this->image_reader_->consume_data(to_send);
if (done) {
this->image_reader_->return_image();
}
}
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
if (state_subs_at_ >= 0) { if (state_subs_at_ >= 0) {
this->process_state_subscriptions_(); this->process_state_subscriptions_();
} }
#endif #endif
#ifdef USE_CAMERA
// Process camera last - state updates are higher priority
// (missing a frame is fine, missing a state update is not)
this->try_send_camera_image_();
#endif
} }
bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) { bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
@@ -447,7 +473,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
if (msg.has_direction) if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction)); call.set_direction(static_cast<fan::FanDirection>(msg.direction));
if (msg.has_preset_mode) if (msg.has_preset_mode)
call.set_preset_mode(msg.preset_mode); call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
call.perform(); call.perform();
} }
#endif #endif
@@ -533,7 +559,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
if (msg.has_flash_length) if (msg.has_flash_length)
call.set_flash_length(msg.flash_length); call.set_flash_length(msg.flash_length);
if (msg.has_effect) if (msg.has_effect)
call.set_effect(reinterpret_cast<const char *>(msg.effect), msg.effect_len); call.set_effect(msg.effect.c_str(), msg.effect.size());
call.perform(); call.perform();
} }
#endif #endif
@@ -712,11 +738,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
if (msg.has_fan_mode) if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode)); call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode) if (msg.has_custom_fan_mode)
call.set_fan_mode(msg.custom_fan_mode); call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
if (msg.has_preset) if (msg.has_preset)
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset)); call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
if (msg.has_custom_preset) if (msg.has_custom_preset)
call.set_preset(msg.custom_preset); call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
if (msg.has_swing_mode) if (msg.has_swing_mode)
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode)); call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
call.perform(); call.perform();
@@ -905,7 +931,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
} }
void APIConnection::select_command(const SelectCommandRequest &msg) { void APIConnection::select_command(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
call.set_option(reinterpret_cast<const char *>(msg.state), msg.state_len); call.set_option(msg.state.c_str(), msg.state.size());
call.perform(); call.perform();
} }
#endif #endif
@@ -1057,6 +1083,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
#endif #endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
void APIConnection::try_send_camera_image_() {
if (!this->image_reader_)
return;
// Send as many chunks as possible without blocking
while (this->image_reader_->available()) {
if (!this->helper_->can_write_without_blocking())
return;
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_->available() == to_send;
CameraImageResponse msg;
msg.key = camera::Camera::instance()->get_object_id_hash();
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
msg.done = done;
#ifdef USE_DEVICES
msg.device_id = camera::Camera::instance()->get_device_id();
#endif
if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
return; // Send failed, try again later
}
this->image_reader_->consume_data(to_send);
if (done) {
this->image_reader_->return_image();
return;
}
}
}
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) { void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
if (!this->flags_.state_subscription) if (!this->flags_.state_subscription)
return; return;
@@ -1064,8 +1120,11 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
return; return;
if (this->image_reader_->available()) if (this->image_reader_->available())
return; return;
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
this->image_reader_->set_image(std::move(image)); this->image_reader_->set_image(std::move(image));
// Try to send immediately to reduce latency
this->try_send_camera_image_();
}
} }
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1094,9 +1153,8 @@ 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 #ifdef USE_TIME_TIMEZONE
if (value.timezone_len > 0) { if (!value.timezone.empty()) {
homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast<const char *>(value.timezone), homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size());
value.timezone_len);
} }
#endif #endif
} }
@@ -1306,6 +1364,57 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
} }
#endif #endif
#ifdef USE_WATER_HEATER
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state,
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
WaterHeaterStateResponse resp;
resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
resp.current_temperature = wh->get_current_temperature();
resp.target_temperature = wh->get_target_temperature();
resp.target_temperature_low = wh->get_target_temperature_low();
resp.target_temperature_high = wh->get_target_temperature_high();
resp.state = wh->get_state();
resp.key = wh->get_object_id_hash();
return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
ListEntitiesWaterHeaterResponse msg;
auto traits = wh->get_traits();
msg.min_temperature = traits.get_min_temperature();
msg.max_temperature = traits.get_max_temperature();
msg.target_temperature_step = traits.get_target_temperature_step();
msg.supported_modes = &traits.get_supported_modes();
msg.supported_features = traits.get_feature_flags();
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE)
call.set_target_temperature(msg.target_temperature);
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW)
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) {
call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
}
call.perform();
}
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const char *event_type) { void APIConnection::send_event(event::Event *event, const char *event_type) {
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
@@ -1412,7 +1521,7 @@ void APIConnection::complete_authentication_() {
} }
bool APIConnection::send_hello_response(const HelloRequest &msg) { bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len); this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size());
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;
@@ -1421,32 +1530,16 @@ 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 = 13; resp.api_version_minor = 14;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise // Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.set_server_info(ESPHOME_VERSION_REF); resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_name(StringRef(App.get_name())); resp.set_name(StringRef(App.get_name()));
#ifdef USE_API_PASSWORD // Auto-authenticate - password auth was removed in ESPHome 2026.1.0
// Password required - wait for authentication
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
#else
// No password configured - auto-authenticate
this->complete_authentication_(); this->complete_authentication_();
#endif
return this->send_message(resp, HelloResponse::MESSAGE_TYPE); return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
} }
#ifdef USE_API_PASSWORD
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
AuthenticationResponse resp;
// bool invalid_password = 1;
resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
if (!resp.invalid_password) {
this->complete_authentication_();
}
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;
@@ -1455,9 +1548,6 @@ bool APIConnection::send_ping_response(const PingRequest &msg) {
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
DeviceInfoResponse resp{}; DeviceInfoResponse resp{};
#ifdef USE_API_PASSWORD
resp.uses_password = true;
#endif
resp.set_name(StringRef(App.get_name())); resp.set_name(StringRef(App.get_name()));
resp.set_friendly_name(StringRef(App.get_friendly_name())); resp.set_friendly_name(StringRef(App.get_friendly_name()));
#ifdef USE_AREAS #ifdef USE_AREAS
@@ -1472,7 +1562,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
resp.set_esphome_version(ESPHOME_VERSION_REF); resp.set_esphome_version(ESPHOME_VERSION_REF);
resp.set_compilation_time(App.get_compilation_time_ref()); // Stack buffer for build time string
char build_time_str[Application::BUILD_TIME_STR_SIZE];
App.get_build_time_string(build_time_str);
resp.set_compilation_time(StringRef(build_time_str));
// Manufacturer string - define once, handle ESP8266 PROGMEM separately // Manufacturer string - define once, handle ESP8266 PROGMEM separately
#if defined(USE_ESP8266) || defined(USE_ESP32) #if defined(USE_ESP8266) || defined(USE_ESP32)
@@ -1579,15 +1672,30 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs()) { // Skip if entity_id is empty (invalid message)
// Compare entity_id and attribute with message fields if (msg.entity_id.empty()) {
bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0); return;
bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) || }
(it.attribute == nullptr && msg.attribute.empty());
if (entity_match && attribute_match) { for (auto &it : this->parent_->get_state_subs()) {
it.callback(msg.state); // Compare entity_id: check length matches and content matches
size_t entity_id_len = strlen(it.entity_id);
if (entity_id_len != msg.entity_id.size() ||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
continue;
} }
// Compare attribute: either both have matching attribute, or both have none
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
if (sub_attr_len != msg.attribute.size() ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
continue;
}
// Create temporary string for callback (callback takes const std::string &)
// Handle empty state
std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size());
it.callback(state);
} }
} }
#endif #endif
@@ -1622,20 +1730,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
// the action list. This ensures async actions (delays, waits) complete first. // the action list. This ensures async actions (delays, waits) complete first.
} }
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) { void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
ExecuteServiceResponse resp; ExecuteServiceResponse resp;
resp.call_id = call_id; resp.call_id = call_id;
resp.success = success; resp.success = success;
resp.set_error_message(StringRef(error_message)); resp.set_error_message(error_message);
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
} }
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) { const uint8_t *response_data, size_t response_data_len) {
ExecuteServiceResponse resp; ExecuteServiceResponse resp;
resp.call_id = call_id; resp.call_id = call_id;
resp.success = success; resp.success = success;
resp.set_error_message(StringRef(error_message)); resp.set_error_message(error_message);
resp.response_data = response_data; resp.response_data = response_data;
resp.response_data_len = response_data_len; resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
@@ -1663,13 +1771,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
resp.success = false; resp.success = false;
psk_t psk{}; psk_t psk{};
if (msg.key.empty()) { if (msg.key_len == 0) {
if (this->parent_->clear_noise_psk(true)) { if (this->parent_->clear_noise_psk(true)) {
resp.success = true; resp.success = true;
} else { } else {
ESP_LOGW(TAG, "Failed to clear encryption key"); ESP_LOGW(TAG, "Failed to clear encryption key");
} }
} else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) { } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length"); ESP_LOGW(TAG, "Invalid encryption key length");
} else if (!this->parent_->save_noise_psk(psk, true)) { } else if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key"); ESP_LOGW(TAG, "Failed to save encryption key");
@@ -1718,12 +1826,6 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
// Do not set last_traffic_ on send // Do not set last_traffic_ on send
return true; return true;
} }
#ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
}
#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 (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());

View File

@@ -24,9 +24,10 @@ struct ClientInfo {
// Keepalive timeout in milliseconds // Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending // Maximum number of entities to process in a single batch during initial state/info sending
// This was increased from 20 to 24 after removing the unique_id field from entity info messages, // API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch
// which reduced message sizes allowing more entities per batch without exceeding packet limits // TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then
static constexpr size_t MAX_INITIAL_PER_BATCH = 24; static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id)
static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id)
// Maximum number of packets to process in a single batch (platform-dependent) // Maximum number of packets to process in a single batch (platform-dependent)
// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_ // This limit exists to prevent stack overflow from the PacketInfo array in process_batch_
// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes // Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes
@@ -176,6 +177,11 @@ class APIConnection final : public APIServerConnection {
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif
#ifdef USE_WATER_HEATER
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
void send_event(event::Event *event, const char *event_type); void send_event(event::Event *event, const char *event_type);
#endif #endif
@@ -197,16 +203,17 @@ 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;
#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;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void subscribe_states(const SubscribeStatesRequest &msg) override { void subscribe_states(const SubscribeStatesRequest &msg) override {
this->flags_.state_subscription = true; this->flags_.state_subscription = true;
this->initial_state_iterator_.begin(); // Start initial state iterator only if no iterator is active
// If list_entities is running, we'll start initial_state when it completes
if (this->active_iterator_ == ActiveIterator::NONE) {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
} }
void subscribe_logs(const SubscribeLogsRequest &msg) override { void subscribe_logs(const SubscribeLogsRequest &msg) override {
this->flags_.log_subscription = msg.level; this->flags_.log_subscription = msg.level;
@@ -224,9 +231,9 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_USER_DEFINED_ACTIONS #ifdef USE_API_USER_DEFINED_ACTIONS
void execute_service(const ExecuteServiceRequest &msg) override; void execute_service(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message); void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len); const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -251,9 +258,6 @@ class APIConnection final : public APIServerConnection {
} }
void on_fatal_error() override; void on_fatal_error() override;
#ifdef USE_API_PASSWORD
void on_unauthenticated_access() override;
#endif
void on_no_setup_connection() override; void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override { ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen // FIXME: ensure no recursive writes can happen
@@ -287,6 +291,10 @@ class APIConnection final : public APIServerConnection {
// Helper function to handle authentication completion // Helper function to handle authentication completion
void complete_authentication_(); void complete_authentication_();
#ifdef USE_CAMERA
void try_send_camera_image_();
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void process_state_subscriptions_(); void process_state_subscriptions_();
#endif #endif
@@ -310,16 +318,15 @@ class APIConnection final : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) { APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types // Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash(); msg.key = entity->get_object_id_hash();
// Try to use static reference first to avoid allocation
StringRef static_ref = entity->get_object_id_ref_for_api_(); // API 1.14+ clients compute object_id client-side from the entity name
// Store dynamic string outside the if-else to maintain lifetime // For older clients, we must send object_id for backward compatibility
std::string object_id; // See: https://github.com/esphome/backlog/issues/76
if (!static_ref.empty()) { // TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
msg.set_object_id(static_ref); // Buffer must remain in scope until encode_message_to_buffer is called
} else { char object_id_buf[OBJECT_ID_MAX_LEN];
// Dynamic case - need to allocate if (!conn->client_supports_api_version(1, 14)) {
object_id = entity->get_object_id(); msg.set_object_id(entity->get_object_id_to(object_id_buf));
msg.set_object_id(StringRef(object_id));
} }
if (entity->has_own_name()) { if (entity->has_own_name()) {
@@ -343,16 +350,24 @@ class APIConnection final : public APIServerConnection {
inline bool check_voice_assistant_api_connection_() const; inline bool check_voice_assistant_api_connection_() const;
#endif #endif
// Get the max batch size based on client API version
// API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch
// TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly
size_t get_max_batch_size_() const {
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
}
// Helper method to process multiple entities from an iterator in a batch // Helper method to process multiple entities from an iterator in a batch
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) { template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
size_t initial_size = this->deferred_batch_.size(); size_t initial_size = this->deferred_batch_.size();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) { size_t max_batch = this->get_max_batch_size_();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
iterator.advance(); iterator.advance();
} }
// If the batch is full, process it immediately // If the batch is full, process it immediately
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_() // Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) { if (this->deferred_batch_.size() >= max_batch) {
this->process_batch_(); this->process_batch_();
} }
} }
@@ -456,6 +471,12 @@ class APIConnection final : public APIServerConnection {
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
#endif #endif
#ifdef USE_WATER_HEATER
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single); uint32_t remaining_size, bool is_single);
@@ -490,10 +511,22 @@ class APIConnection final : public APIServerConnection {
std::unique_ptr<APIFrameHelper> helper_; std::unique_ptr<APIFrameHelper> helper_;
APIServer *parent_; APIServer *parent_;
// Group 2: Larger objects (must be 4-byte aligned) // Group 2: Iterator union (saves ~16 bytes vs separate iterators)
// These contain vectors/pointers internally, so putting them early ensures good alignment // These iterators are never active simultaneously - list_entities runs to completion
InitialStateIterator initial_state_iterator_; // before initial_state begins, so we use a union with explicit construction/destruction.
ListEntitiesIterator list_entities_iterator_; enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE };
union IteratorUnion {
ListEntitiesIterator list_entities;
InitialStateIterator initial_state;
// Constructor/destructor do nothing - use placement new/explicit destructor
IteratorUnion() {}
~IteratorUnion() {}
} iterator_storage_;
// Helper methods for iterator lifecycle management
void destroy_active_iterator_();
void begin_iterator_(ActiveIterator type);
#ifdef USE_CAMERA #ifdef USE_CAMERA
std::unique_ptr<camera::CameraImageReader> image_reader_; std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif #endif
@@ -608,7 +641,9 @@ class APIConnection final : public APIServerConnection {
// 2-byte types immediately after flags_ (no padding between them) // 2-byte types immediately after flags_ (no padding between them)
uint16_t client_api_version_major_{0}; uint16_t client_api_version_major_{0};
uint16_t client_api_version_minor_{0}; uint16_t client_api_version_minor_{0};
// Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary // 1-byte type to fill padding
ActiveIterator active_iterator_{ActiveIterator::NONE};
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
uint32_t get_batch_delay_ms_() const; uint32_t get_batch_delay_ms_() const;
// Message will use 8 more bytes than the minimum size, and typical // Message will use 8 more bytes than the minimum size, and typical

View File

@@ -13,12 +13,26 @@ namespace esphome::api {
static const char *const TAG = "api.frame_helper"; static const char *const TAG = "api.frame_helper";
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#define HELPER_LOG(msg, ...) \ #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) 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) \
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#else #else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -24,12 +24,26 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif #endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#define HELPER_LOG(msg, ...) \ #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) 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) \
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#else #else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -18,12 +18,26 @@ namespace esphome::api {
static const char *const TAG = "api.plaintext"; static const char *const TAG = "api.plaintext";
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#define HELPER_LOG(msg, ...) \ #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) 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) \
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#else #else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0)

View File

@@ -23,9 +23,7 @@ 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: {
// Use raw data directly to avoid allocation this->client_info = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
this->client_info = value.data();
this->client_info_len = value.size();
break; break;
} }
default: default:
@@ -45,23 +43,6 @@ 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());
} }
#ifdef USE_API_PASSWORD
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->password = value.data();
this->password_len = value.size();
break;
}
default:
return false;
}
return true;
}
void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_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);
@@ -85,9 +66,6 @@ void DeviceInfo::calculate_size(ProtoSize &size) const {
} }
#endif #endif
void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_API_PASSWORD
buffer.encode_bool(1, this->uses_password);
#endif
buffer.encode_string(2, this->name_ref_); buffer.encode_string(2, this->name_ref_);
buffer.encode_string(3, this->mac_address_ref_); buffer.encode_string(3, this->mac_address_ref_);
buffer.encode_string(4, this->esphome_version_ref_); buffer.encode_string(4, this->esphome_version_ref_);
@@ -124,12 +102,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#endif #endif
#ifdef USE_DEVICES #ifdef USE_DEVICES
for (const auto &it : this->devices) { for (const auto &it : this->devices) {
buffer.encode_message(20, it, true); buffer.encode_message(20, it);
} }
#endif #endif
#ifdef USE_AREAS #ifdef USE_AREAS
for (const auto &it : this->areas) { for (const auto &it : this->areas) {
buffer.encode_message(21, it, true); buffer.encode_message(21, it);
} }
#endif #endif
#ifdef USE_AREAS #ifdef USE_AREAS
@@ -143,9 +121,6 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#endif #endif
} }
void DeviceInfoResponse::calculate_size(ProtoSize &size) const { void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_API_PASSWORD
size.add_bool(1, this->uses_password);
#endif
size.add_length(1, this->name_ref_.size()); size.add_length(1, this->name_ref_.size());
size.add_length(1, this->mac_address_ref_.size()); size.add_length(1, this->mac_address_ref_.size());
size.add_length(1, this->esphome_version_ref_.size()); size.add_length(1, this->esphome_version_ref_.size());
@@ -447,9 +422,10 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 13: case 13: {
this->preset_mode = value.as_string(); this->preset_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -612,9 +588,7 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 19: { case 19: {
// Use raw data directly to avoid allocation this->effect = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
this->effect = value.data();
this->effect_len = value.size();
break; break;
} }
default: default:
@@ -855,9 +829,11 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->key = value.as_string(); this->key = value.data();
this->key_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -878,13 +854,13 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const {
void HomeassistantActionRequest::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);
} }
for (auto &it : this->data_template) { for (auto &it : this->data_template) {
buffer.encode_message(3, it, true); buffer.encode_message(3, it);
} }
for (auto &it : this->variables) { for (auto &it : this->variables) {
buffer.encode_message(4, it, true); buffer.encode_message(4, it);
} }
buffer.encode_bool(5, this->is_event); buffer.encode_bool(5, this->is_event);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
@@ -930,12 +906,12 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v
} }
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 3: case 3: {
this->error_message = value.as_string(); this->error_message = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
case 4: { case 4: {
// Use raw data directly to avoid allocation
this->response_data = value.data(); this->response_data = value.data();
this->response_data_len = value.size(); this->response_data_len = value.size();
break; break;
@@ -960,15 +936,18 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const
} }
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->entity_id = value.as_string(); this->entity_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 2: }
this->state = value.as_string(); case 2: {
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 3: }
this->attribute = value.as_string(); case 3: {
this->attribute = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -978,9 +957,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
// Use raw data directly to avoid allocation this->timezone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
this->timezone = value.data();
this->timezone_len = value.size();
break; break;
} }
default: default:
@@ -1011,7 +988,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_); buffer.encode_string(1, this->name_ref_);
buffer.encode_fixed32(2, this->key); buffer.encode_fixed32(2, this->key);
for (auto &it : this->args) { for (auto &it : this->args) {
buffer.encode_message(3, it, true); buffer.encode_message(3, it);
} }
buffer.encode_uint32(4, static_cast<uint32_t>(this->supports_response)); buffer.encode_uint32(4, static_cast<uint32_t>(this->supports_response));
} }
@@ -1045,9 +1022,10 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value)
} }
bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 4: case 4: {
this->string_ = value.as_string(); this->string_ = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
case 9: case 9:
this->string_array.push_back(value.as_string()); this->string_array.push_back(value.as_string());
break; break;
@@ -1138,7 +1116,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const {
size.add_bool(1, this->success); size.add_bool(1, this->success);
size.add_length(1, this->error_message_ref_.size()); size.add_length(1, this->error_message_ref_.size());
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
size.add_length(4, this->response_data_len); size.add_length(1, this->response_data_len);
#endif #endif
} }
#endif #endif
@@ -1392,12 +1370,14 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
} }
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 17: case 17: {
this->custom_fan_mode = value.as_string(); this->custom_fan_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 21: }
this->custom_preset = value.as_string(); case 21: {
this->custom_preset = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -1426,6 +1406,114 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
return true; return true;
} }
#endif #endif
#ifdef USE_WATER_HEATER
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id_ref_);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name_ref_);
#ifdef USE_ENTITY_ICON
buffer.encode_string(4, this->icon_ref_);
#endif
buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_uint32(6, static_cast<uint32_t>(this->entity_category));
#ifdef USE_DEVICES
buffer.encode_uint32(7, this->device_id);
#endif
buffer.encode_float(8, this->min_temperature);
buffer.encode_float(9, this->max_temperature);
buffer.encode_float(10, this->target_temperature_step);
for (const auto &it : *this->supported_modes) {
buffer.encode_uint32(11, static_cast<uint32_t>(it), true);
}
buffer.encode_uint32(12, this->supported_features);
}
void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->object_id_ref_.size());
size.add_fixed32(1, this->key);
size.add_length(1, this->name_ref_.size());
#ifdef USE_ENTITY_ICON
size.add_length(1, this->icon_ref_.size());
#endif
size.add_bool(1, this->disabled_by_default);
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
#ifdef USE_DEVICES
size.add_uint32(1, this->device_id);
#endif
size.add_float(1, this->min_temperature);
size.add_float(1, this->max_temperature);
size.add_float(1, this->target_temperature_step);
if (!this->supported_modes->empty()) {
for (const auto &it : *this->supported_modes) {
size.add_uint32_force(1, static_cast<uint32_t>(it));
}
}
size.add_uint32(1, this->supported_features);
}
void WaterHeaterStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_float(2, this->current_temperature);
buffer.encode_float(3, this->target_temperature);
buffer.encode_uint32(4, static_cast<uint32_t>(this->mode));
#ifdef USE_DEVICES
buffer.encode_uint32(5, this->device_id);
#endif
buffer.encode_uint32(6, this->state);
buffer.encode_float(7, this->target_temperature_low);
buffer.encode_float(8, this->target_temperature_high);
}
void WaterHeaterStateResponse::calculate_size(ProtoSize &size) const {
size.add_fixed32(1, this->key);
size.add_float(1, this->current_temperature);
size.add_float(1, this->target_temperature);
size.add_uint32(1, static_cast<uint32_t>(this->mode));
#ifdef USE_DEVICES
size.add_uint32(1, this->device_id);
#endif
size.add_uint32(1, this->state);
size.add_float(1, this->target_temperature_low);
size.add_float(1, this->target_temperature_high);
}
bool WaterHeaterCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2:
this->has_fields = value.as_uint32();
break;
case 3:
this->mode = static_cast<enums::WaterHeaterMode>(value.as_uint32());
break;
#ifdef USE_DEVICES
case 5:
this->device_id = value.as_uint32();
break;
#endif
case 6:
this->state = value.as_uint32();
break;
default:
return false;
}
return true;
}
bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1:
this->key = value.as_fixed32();
break;
case 4:
this->target_temperature = value.as_float();
break;
case 7:
this->target_temperature_low = value.as_float();
break;
case 8:
this->target_temperature_high = value.as_float();
break;
default:
return false;
}
return true;
}
#endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id_ref_); buffer.encode_string(1, this->object_id_ref_);
@@ -1573,9 +1661,7 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
// Use raw data directly to avoid allocation this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
this->state = value.data();
this->state_len = value.size();
break; break;
} }
default: default:
@@ -1603,8 +1689,8 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon_ref_); buffer.encode_string(5, this->icon_ref_);
#endif #endif
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
for (auto &it : this->tones) { for (const char *it : *this->tones) {
buffer.encode_string(7, it, true); buffer.encode_string(7, it, strlen(it), true);
} }
buffer.encode_bool(8, this->supports_duration); buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume); buffer.encode_bool(9, this->supports_volume);
@@ -1621,9 +1707,9 @@ void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->icon_ref_.size()); size.add_length(1, this->icon_ref_.size());
#endif #endif
size.add_bool(1, this->disabled_by_default); size.add_bool(1, this->disabled_by_default);
if (!this->tones.empty()) { if (!this->tones->empty()) {
for (const auto &it : this->tones) { for (const char *it : *this->tones) {
size.add_length_force(1, it.size()); size.add_length_force(1, strlen(it));
} }
} }
size.add_bool(1, this->supports_duration); size.add_bool(1, this->supports_duration);
@@ -1679,9 +1765,10 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 5: case 5: {
this->tone = value.as_string(); this->tone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -1770,9 +1857,10 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 4: case 4: {
this->code = value.as_string(); this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -1867,7 +1955,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category)); buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
buffer.encode_bool(8, this->supports_pause); buffer.encode_bool(8, this->supports_pause);
for (auto &it : this->supported_formats) { for (auto &it : this->supported_formats) {
buffer.encode_message(9, it, true); buffer.encode_message(9, it);
} }
#ifdef USE_DEVICES #ifdef USE_DEVICES
buffer.encode_uint32(10, this->device_id); buffer.encode_uint32(10, this->device_id);
@@ -1940,9 +2028,10 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
} }
bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 7: case 7: {
this->media_url = value.as_string(); this->media_url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -1987,7 +2076,7 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const {
} }
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const { void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
for (uint16_t i = 0; i < this->advertisements_len; i++) { for (uint16_t i = 0; i < this->advertisements_len; i++) {
buffer.encode_message(1, this->advertisements[i], true); buffer.encode_message(1, this->advertisements[i]);
} }
} }
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const { void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
@@ -2060,7 +2149,7 @@ void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(2, this->handle); buffer.encode_uint32(2, this->handle);
buffer.encode_uint32(3, this->properties); buffer.encode_uint32(3, this->properties);
for (auto &it : this->descriptors) { for (auto &it : this->descriptors) {
buffer.encode_message(4, it, true); buffer.encode_message(4, it);
} }
buffer.encode_uint32(5, this->short_uuid); buffer.encode_uint32(5, this->short_uuid);
} }
@@ -2081,7 +2170,7 @@ void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_uint32(2, this->handle); buffer.encode_uint32(2, this->handle);
for (auto &it : this->characteristics) { for (auto &it : this->characteristics) {
buffer.encode_message(3, it, true); buffer.encode_message(3, it);
} }
buffer.encode_uint32(4, this->short_uuid); buffer.encode_uint32(4, this->short_uuid);
} }
@@ -2097,7 +2186,7 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const {
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address); buffer.encode_uint64(1, this->address);
for (auto &it : this->services) { for (auto &it : this->services) {
buffer.encode_message(2, it, true); buffer.encode_message(2, it);
} }
} }
void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const { void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const {
@@ -2150,7 +2239,6 @@ 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: {
// Use raw data directly to avoid allocation
this->data = value.data(); this->data = value.data();
this->data_len = value.size(); this->data_len = value.size();
break; break;
@@ -2189,7 +2277,6 @@ 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: {
// Use raw data directly to avoid allocation
this->data = value.data(); this->data = value.data();
this->data_len = value.size(); this->data_len = value.size();
break; break;
@@ -2373,12 +2460,14 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
} }
bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->name = value.as_string(); this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 2: }
this->value = value.as_string(); case 2: {
this->value = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -2417,20 +2506,22 @@ bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->data = value.as_string(); this->data = value.data();
this->data_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
return true; return true;
} }
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bytes(1, this->data_ptr_, this->data_len_); buffer.encode_bytes(1, this->data, this->data_len);
buffer.encode_bool(2, this->end); buffer.encode_bool(2, this->end);
} }
void VoiceAssistantAudio::calculate_size(ProtoSize &size) const { void VoiceAssistantAudio::calculate_size(ProtoSize &size) const {
size.add_length(1, this->data_len_); size.add_length(1, this->data_len);
size.add_bool(1, this->end); size.add_bool(1, this->end);
} }
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@@ -2454,12 +2545,14 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar
} }
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 2: case 2: {
this->timer_id = value.as_string(); this->timer_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 3: }
this->name = value.as_string(); case 3: {
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -2477,15 +2570,18 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt
} }
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->media_id = value.as_string(); this->media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 2: }
this->text = value.as_string(); case 2: {
this->text = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 3: }
this->preannounce_media_id = value.as_string(); case 3: {
this->preannounce_media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -2521,24 +2617,29 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn
} }
bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->id = value.as_string(); this->id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 2: }
this->wake_word = value.as_string(); case 2: {
this->wake_word = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
case 3: case 3:
this->trained_languages.push_back(value.as_string()); this->trained_languages.push_back(value.as_string());
break; break;
case 4: case 4: {
this->model_type = value.as_string(); this->model_type = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 6: }
this->model_hash = value.as_string(); case 6: {
this->model_hash = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
case 7: }
this->url = value.as_string(); case 7: {
this->url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -2557,7 +2658,7 @@ bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoL
} }
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);
} }
for (const auto &it : *this->active_wake_words) { for (const auto &it : *this->active_wake_words) {
buffer.encode_string(2, it, true); buffer.encode_string(2, it, true);
@@ -2648,9 +2749,10 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI
} }
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 3: case 3: {
this->code = value.as_string(); this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -2732,9 +2834,10 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 2: case 2: {
this->state = value.as_string(); this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break; break;
}
default: default:
return false; return false;
} }
@@ -3202,7 +3305,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
// Use raw data directly to avoid allocation
this->data = value.data(); this->data = value.data();
this->data_len = value.size(); this->data_len = value.size();
break; break;
@@ -3227,7 +3329,6 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
// Use raw data directly to avoid allocation
this->data = value.data(); this->data = value.data();
this->data_len = value.size(); this->data_len = value.size();
break; break;
@@ -3243,7 +3344,7 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
} }
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const { void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->type)); size.add_uint32(1, static_cast<uint32_t>(this->type));
size.add_length(2, this->data_len); size.add_length(1, this->data_len);
} }
#endif #endif

View File

@@ -129,6 +129,25 @@ enum ClimatePreset : uint32_t {
CLIMATE_PRESET_ACTIVITY = 7, CLIMATE_PRESET_ACTIVITY = 7,
}; };
#endif #endif
#ifdef USE_WATER_HEATER
enum WaterHeaterMode : uint32_t {
WATER_HEATER_MODE_OFF = 0,
WATER_HEATER_MODE_ECO = 1,
WATER_HEATER_MODE_ELECTRIC = 2,
WATER_HEATER_MODE_PERFORMANCE = 3,
WATER_HEATER_MODE_HIGH_DEMAND = 4,
WATER_HEATER_MODE_HEAT_PUMP = 5,
WATER_HEATER_MODE_GAS = 6,
};
#endif
enum WaterHeaterCommandHasField : uint32_t {
WATER_HEATER_COMMAND_HAS_NONE = 0,
WATER_HEATER_COMMAND_HAS_MODE = 1,
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2,
WATER_HEATER_COMMAND_HAS_STATE = 4,
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8,
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16,
};
#ifdef USE_NUMBER #ifdef USE_NUMBER
enum NumberMode : uint32_t { enum NumberMode : uint32_t {
NUMBER_MODE_AUTO = 0, NUMBER_MODE_AUTO = 0,
@@ -338,12 +357,11 @@ 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 = 27; static constexpr uint8_t ESTIMATED_SIZE = 17;
#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
const uint8_t *client_info{nullptr}; StringRef client_info{};
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
@@ -375,40 +393,6 @@ class HelloResponse final : public ProtoMessage {
protected: protected:
}; };
#ifdef USE_API_PASSWORD
class AuthenticationRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 3;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_request"; }
#endif
const uint8_t *password{nullptr};
uint16_t password_len{0};
#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 AuthenticationResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 4;
static constexpr uint8_t ESTIMATED_SIZE = 2;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_response"; }
#endif
bool invalid_password{false};
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:
};
#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;
@@ -508,12 +492,9 @@ 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 uint16_t ESTIMATED_SIZE = 257; static constexpr uint8_t ESTIMATED_SIZE = 255;
#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
#ifdef USE_API_PASSWORD
bool uses_password{false};
#endif #endif
StringRef name_ref_{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; } void set_name(const StringRef &ref) { this->name_ref_ = ref; }
@@ -778,7 +759,7 @@ class FanCommandRequest final : public CommandProtoMessage {
bool has_speed_level{false}; bool has_speed_level{false};
int32_t speed_level{0}; int32_t speed_level{0};
bool has_preset_mode{false}; bool has_preset_mode{false};
std::string preset_mode{}; StringRef preset_mode{};
#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
@@ -840,7 +821,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
class LightCommandRequest final : public CommandProtoMessage { class LightCommandRequest final : public CommandProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 32; static constexpr uint8_t MESSAGE_TYPE = 32;
static constexpr uint8_t ESTIMATED_SIZE = 122; static constexpr uint8_t ESTIMATED_SIZE = 112;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "light_command_request"; } const char *message_name() const override { return "light_command_request"; }
#endif #endif
@@ -869,8 +850,7 @@ class LightCommandRequest final : public CommandProtoMessage {
bool has_flash_length{false}; bool has_flash_length{false};
uint32_t flash_length{0}; uint32_t flash_length{0};
bool has_effect{false}; bool has_effect{false};
const uint8_t *effect{nullptr}; StringRef effect{};
uint16_t effect_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
@@ -1030,7 +1010,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
class SubscribeLogsResponse final : public ProtoMessage { class SubscribeLogsResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 29; static constexpr uint8_t MESSAGE_TYPE = 29;
static constexpr uint8_t ESTIMATED_SIZE = 11; static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_logs_response"; } const char *message_name() const override { return "subscribe_logs_response"; }
#endif #endif
@@ -1053,11 +1033,12 @@ class SubscribeLogsResponse final : public ProtoMessage {
class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 124; static constexpr uint8_t MESSAGE_TYPE = 124;
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 "noise_encryption_set_key_request"; } const char *message_name() const override { return "noise_encryption_set_key_request"; }
#endif #endif
std::string key{}; const uint8_t *key{nullptr};
uint16_t key_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
@@ -1150,7 +1131,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
#endif #endif
uint32_t call_id{0}; uint32_t call_id{0};
bool success{false}; bool success{false};
std::string error_message{}; StringRef error_message{};
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr}; const uint8_t *response_data{nullptr};
uint16_t response_data_len{0}; uint16_t response_data_len{0};
@@ -1205,9 +1186,9 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "home_assistant_state_response"; } const char *message_name() const override { return "home_assistant_state_response"; }
#endif #endif
std::string entity_id{}; StringRef entity_id{};
std::string state{}; StringRef state{};
std::string attribute{}; StringRef attribute{};
#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
@@ -1232,13 +1213,12 @@ 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 = 24; static constexpr uint8_t ESTIMATED_SIZE = 14;
#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};
const uint8_t *timezone{nullptr}; StringRef timezone{};
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
@@ -1286,7 +1266,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
bool bool_{false}; bool bool_{false};
int32_t legacy_int{0}; int32_t legacy_int{0};
float float_{0.0f}; float float_{0.0f};
std::string string_{}; StringRef string_{};
int32_t int_{0}; int32_t int_{0};
FixedVector<bool> bool_array{}; FixedVector<bool> bool_array{};
FixedVector<int32_t> int_array{}; FixedVector<int32_t> int_array{};
@@ -1372,7 +1352,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
class CameraImageResponse final : public StateResponseProtoMessage { class CameraImageResponse final : public StateResponseProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 44; static constexpr uint8_t MESSAGE_TYPE = 44;
static constexpr uint8_t ESTIMATED_SIZE = 20; static constexpr uint8_t ESTIMATED_SIZE = 30;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "camera_image_response"; } const char *message_name() const override { return "camera_image_response"; }
#endif #endif
@@ -1492,11 +1472,11 @@ class ClimateCommandRequest final : public CommandProtoMessage {
bool has_swing_mode{false}; bool has_swing_mode{false};
enums::ClimateSwingMode swing_mode{}; enums::ClimateSwingMode swing_mode{};
bool has_custom_fan_mode{false}; bool has_custom_fan_mode{false};
std::string custom_fan_mode{}; StringRef custom_fan_mode{};
bool has_preset{false}; bool has_preset{false};
enums::ClimatePreset preset{}; enums::ClimatePreset preset{};
bool has_custom_preset{false}; bool has_custom_preset{false};
std::string custom_preset{}; StringRef custom_preset{};
bool has_target_humidity{false}; bool has_target_humidity{false};
float target_humidity{0.0f}; float target_humidity{0.0f};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1509,6 +1489,70 @@ class ClimateCommandRequest 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_WATER_HEATER
class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 132;
static constexpr uint8_t ESTIMATED_SIZE = 63;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_water_heater_response"; }
#endif
float min_temperature{0.0f};
float max_temperature{0.0f};
float target_temperature_step{0.0f};
const water_heater::WaterHeaterModeMask *supported_modes{};
uint32_t supported_features{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:
};
class WaterHeaterStateResponse final : public StateResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 133;
static constexpr uint8_t ESTIMATED_SIZE = 35;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "water_heater_state_response"; }
#endif
float current_temperature{0.0f};
float target_temperature{0.0f};
enums::WaterHeaterMode mode{};
uint32_t state{0};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
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:
};
class WaterHeaterCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 134;
static constexpr uint8_t ESTIMATED_SIZE = 34;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "water_heater_command_request"; }
#endif
uint32_t has_fields{0};
enums::WaterHeaterMode mode{};
float target_temperature{0.0f};
uint32_t state{0};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
#endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
public: public:
@@ -1605,12 +1649,11 @@ class SelectStateResponse final : public StateResponseProtoMessage {
class SelectCommandRequest final : public CommandProtoMessage { class SelectCommandRequest final : public CommandProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 54; static constexpr uint8_t MESSAGE_TYPE = 54;
static constexpr uint8_t ESTIMATED_SIZE = 28; static constexpr uint8_t ESTIMATED_SIZE = 18;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_command_request"; } const char *message_name() const override { return "select_command_request"; }
#endif #endif
const uint8_t *state{nullptr}; StringRef state{};
uint16_t state_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
@@ -1629,7 +1672,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_siren_response"; } const char *message_name() const override { return "list_entities_siren_response"; }
#endif #endif
std::vector<std::string> tones{}; const FixedVector<const char *> *tones{};
bool supports_duration{false}; bool supports_duration{false};
bool supports_volume{false}; bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
@@ -1666,7 +1709,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
bool has_state{false}; bool has_state{false};
bool state{false}; bool state{false};
bool has_tone{false}; bool has_tone{false};
std::string tone{}; StringRef tone{};
bool has_duration{false}; bool has_duration{false};
uint32_t duration{0}; uint32_t duration{0};
bool has_volume{false}; bool has_volume{false};
@@ -1727,7 +1770,7 @@ class LockCommandRequest final : public CommandProtoMessage {
#endif #endif
enums::LockCommand command{}; enums::LockCommand command{};
bool has_code{false}; bool has_code{false};
std::string code{}; StringRef code{};
#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
@@ -1837,7 +1880,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
bool has_volume{false}; bool has_volume{false};
float volume{0.0f}; float volume{0.0f};
bool has_media_url{false}; bool has_media_url{false};
std::string media_url{}; StringRef media_url{};
bool has_announcement{false}; bool has_announcement{false};
bool announcement{false}; bool announcement{false};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2044,7 +2087,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
class BluetoothGATTReadResponse final : public ProtoMessage { class BluetoothGATTReadResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 74; static constexpr uint8_t MESSAGE_TYPE = 74;
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_read_response"; } const char *message_name() const override { return "bluetooth_gatt_read_response"; }
#endif #endif
@@ -2139,7 +2182,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
class BluetoothGATTNotifyDataResponse final : public ProtoMessage { class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 79; static constexpr uint8_t MESSAGE_TYPE = 79;
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_notify_data_response"; } const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; }
#endif #endif
@@ -2413,8 +2456,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
}; };
class VoiceAssistantEventData final : public ProtoDecodableMessage { class VoiceAssistantEventData final : public ProtoDecodableMessage {
public: public:
std::string name{}; StringRef name{};
std::string value{}; StringRef value{};
#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
@@ -2442,17 +2485,12 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
class VoiceAssistantAudio final : public ProtoDecodableMessage { class VoiceAssistantAudio final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 106; static constexpr uint8_t MESSAGE_TYPE = 106;
static constexpr uint8_t ESTIMATED_SIZE = 11; static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_audio"; } const char *message_name() const override { return "voice_assistant_audio"; }
#endif #endif
std::string data{}; const uint8_t *data{nullptr};
const uint8_t *data_ptr_{nullptr}; uint16_t data_len{0};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
bool end{false}; bool end{false};
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;
@@ -2472,8 +2510,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
const char *message_name() const override { return "voice_assistant_timer_event_response"; } const char *message_name() const override { return "voice_assistant_timer_event_response"; }
#endif #endif
enums::VoiceAssistantTimerEvent event_type{}; enums::VoiceAssistantTimerEvent event_type{};
std::string timer_id{}; StringRef timer_id{};
std::string name{}; StringRef name{};
uint32_t total_seconds{0}; uint32_t total_seconds{0};
uint32_t seconds_left{0}; uint32_t seconds_left{0};
bool is_active{false}; bool is_active{false};
@@ -2492,9 +2530,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_announce_request"; } const char *message_name() const override { return "voice_assistant_announce_request"; }
#endif #endif
std::string media_id{}; StringRef media_id{};
std::string text{}; StringRef text{};
std::string preannounce_media_id{}; StringRef preannounce_media_id{};
bool start_conversation{false}; bool start_conversation{false};
#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;
@@ -2537,13 +2575,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
}; };
class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage { class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
public: public:
std::string id{}; StringRef id{};
std::string wake_word{}; StringRef wake_word{};
std::vector<std::string> trained_languages{}; std::vector<std::string> trained_languages{};
std::string model_type{}; StringRef model_type{};
uint32_t model_size{0}; uint32_t model_size{0};
std::string model_hash{}; StringRef model_hash{};
std::string url{}; StringRef 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
@@ -2644,7 +2682,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
const char *message_name() const override { return "alarm_control_panel_command_request"; } const char *message_name() const override { return "alarm_control_panel_command_request"; }
#endif #endif
enums::AlarmControlPanelStateCommand command{}; enums::AlarmControlPanelStateCommand command{};
std::string code{}; StringRef code{};
#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
@@ -2701,7 +2739,7 @@ class TextCommandRequest final : public CommandProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_command_request"; } const char *message_name() const override { return "text_command_request"; }
#endif #endif
std::string state{}; StringRef state{};
#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

View File

@@ -348,6 +348,47 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
} }
} }
#endif #endif
#ifdef USE_WATER_HEATER
template<> const char *proto_enum_to_string<enums::WaterHeaterMode>(enums::WaterHeaterMode value) {
switch (value) {
case enums::WATER_HEATER_MODE_OFF:
return "WATER_HEATER_MODE_OFF";
case enums::WATER_HEATER_MODE_ECO:
return "WATER_HEATER_MODE_ECO";
case enums::WATER_HEATER_MODE_ELECTRIC:
return "WATER_HEATER_MODE_ELECTRIC";
case enums::WATER_HEATER_MODE_PERFORMANCE:
return "WATER_HEATER_MODE_PERFORMANCE";
case enums::WATER_HEATER_MODE_HIGH_DEMAND:
return "WATER_HEATER_MODE_HIGH_DEMAND";
case enums::WATER_HEATER_MODE_HEAT_PUMP:
return "WATER_HEATER_MODE_HEAT_PUMP";
case enums::WATER_HEATER_MODE_GAS:
return "WATER_HEATER_MODE_GAS";
default:
return "UNKNOWN";
}
}
#endif
template<>
const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::WaterHeaterCommandHasField value) {
switch (value) {
case enums::WATER_HEATER_COMMAND_HAS_NONE:
return "WATER_HEATER_COMMAND_HAS_NONE";
case enums::WATER_HEATER_COMMAND_HAS_MODE:
return "WATER_HEATER_COMMAND_HAS_MODE";
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE:
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE";
case enums::WATER_HEATER_COMMAND_HAS_STATE:
return "WATER_HEATER_COMMAND_HAS_STATE";
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW:
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW";
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH:
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH";
default:
return "UNKNOWN";
}
}
#ifdef USE_NUMBER #ifdef USE_NUMBER
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) { template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
switch (value) { switch (value) {
@@ -695,7 +736,7 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
void HelloRequest::dump_to(std::string &out) const { void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest"); MessageDumpHelper helper(out, "HelloRequest");
out.append(" client_info: "); out.append(" client_info: ");
out.append(format_hex_pretty(this->client_info, this->client_info_len)); out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'");
out.append("\n"); 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);
@@ -707,18 +748,6 @@ 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_);
} }
#ifdef USE_API_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 {}"); }
@@ -741,9 +770,6 @@ void DeviceInfo::dump_to(std::string &out) const {
#endif #endif
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "DeviceInfoResponse"); MessageDumpHelper helper(out, "DeviceInfoResponse");
#ifdef USE_API_PASSWORD
dump_field(out, "uses_password", this->uses_password);
#endif
dump_field(out, "name", this->name_ref_); dump_field(out, "name", this->name_ref_);
dump_field(out, "mac_address", this->mac_address_ref_); dump_field(out, "mac_address", this->mac_address_ref_);
dump_field(out, "esphome_version", this->esphome_version_ref_); dump_field(out, "esphome_version", this->esphome_version_ref_);
@@ -923,7 +949,9 @@ void FanCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_speed_level", this->has_speed_level); dump_field(out, "has_speed_level", this->has_speed_level);
dump_field(out, "speed_level", this->speed_level); dump_field(out, "speed_level", this->speed_level);
dump_field(out, "has_preset_mode", this->has_preset_mode); dump_field(out, "has_preset_mode", this->has_preset_mode);
dump_field(out, "preset_mode", this->preset_mode); out.append(" preset_mode: ");
out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
#endif #endif
@@ -1000,7 +1028,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
dump_field(out, "flash_length", this->flash_length); dump_field(out, "flash_length", this->flash_length);
dump_field(out, "has_effect", this->has_effect); dump_field(out, "has_effect", this->has_effect);
out.append(" effect: "); out.append(" effect: ");
out.append(format_hex_pretty(this->effect, this->effect_len)); out.append("'").append(this->effect.c_str(), this->effect.size()).append("'");
out.append("\n"); out.append("\n");
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
@@ -1113,7 +1141,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest"); MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
out.append(" key: "); out.append(" key: ");
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->key.data()), this->key.size())); out.append(format_hex_pretty(this->key, this->key_len));
out.append("\n"); out.append("\n");
} }
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); } void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
@@ -1162,7 +1190,9 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantActionResponse"); MessageDumpHelper helper(out, "HomeassistantActionResponse");
dump_field(out, "call_id", this->call_id); dump_field(out, "call_id", this->call_id);
dump_field(out, "success", this->success); dump_field(out, "success", this->success);
dump_field(out, "error_message", this->error_message); out.append(" error_message: ");
out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'");
out.append("\n");
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
out.append(" response_data: "); out.append(" response_data: ");
out.append(format_hex_pretty(this->response_data, this->response_data_len)); out.append(format_hex_pretty(this->response_data, this->response_data_len));
@@ -1182,9 +1212,15 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
} }
void HomeAssistantStateResponse::dump_to(std::string &out) const { void HomeAssistantStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeAssistantStateResponse"); MessageDumpHelper helper(out, "HomeAssistantStateResponse");
dump_field(out, "entity_id", this->entity_id); out.append(" entity_id: ");
dump_field(out, "state", this->state); out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'");
dump_field(out, "attribute", this->attribute); out.append("\n");
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
out.append(" attribute: ");
out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'");
out.append("\n");
} }
#endif #endif
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
@@ -1192,7 +1228,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse"); MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds); dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: "); out.append(" timezone: ");
out.append(format_hex_pretty(this->timezone, this->timezone_len)); out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'");
out.append("\n"); out.append("\n");
} }
#ifdef USE_API_USER_DEFINED_ACTIONS #ifdef USE_API_USER_DEFINED_ACTIONS
@@ -1217,7 +1253,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "bool_", this->bool_); dump_field(out, "bool_", this->bool_);
dump_field(out, "legacy_int", this->legacy_int); dump_field(out, "legacy_int", this->legacy_int);
dump_field(out, "float_", this->float_); dump_field(out, "float_", this->float_);
dump_field(out, "string_", this->string_); out.append(" string_: ");
out.append("'").append(this->string_.c_str(), this->string_.size()).append("'");
out.append("\n");
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", static_cast<bool>(it), 4); dump_field(out, "bool_array", static_cast<bool>(it), 4);
@@ -1374,11 +1412,15 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_swing_mode", this->has_swing_mode); dump_field(out, "has_swing_mode", this->has_swing_mode);
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode)); dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode); dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode);
dump_field(out, "custom_fan_mode", this->custom_fan_mode); out.append(" custom_fan_mode: ");
out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'");
out.append("\n");
dump_field(out, "has_preset", this->has_preset); dump_field(out, "has_preset", this->has_preset);
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset)); dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
dump_field(out, "has_custom_preset", this->has_custom_preset); dump_field(out, "has_custom_preset", this->has_custom_preset);
dump_field(out, "custom_preset", this->custom_preset); out.append(" custom_preset: ");
out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'");
out.append("\n");
dump_field(out, "has_target_humidity", this->has_target_humidity); dump_field(out, "has_target_humidity", this->has_target_humidity);
dump_field(out, "target_humidity", this->target_humidity); dump_field(out, "target_humidity", this->target_humidity);
#ifdef USE_DEVICES #ifdef USE_DEVICES
@@ -1386,6 +1428,55 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
#endif #endif
} }
#endif #endif
#ifdef USE_WATER_HEATER
void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "min_temperature", this->min_temperature);
dump_field(out, "max_temperature", this->max_temperature);
dump_field(out, "target_temperature_step", this->target_temperature_step);
for (const auto &it : *this->supported_modes) {
dump_field(out, "supported_modes", static_cast<enums::WaterHeaterMode>(it), 4);
}
dump_field(out, "supported_features", this->supported_features);
}
void WaterHeaterStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "WaterHeaterStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "current_temperature", this->current_temperature);
dump_field(out, "target_temperature", this->target_temperature);
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "state", this->state);
dump_field(out, "target_temperature_low", this->target_temperature_low);
dump_field(out, "target_temperature_high", this->target_temperature_high);
}
void WaterHeaterCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "WaterHeaterCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "has_fields", this->has_fields);
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
dump_field(out, "target_temperature", this->target_temperature);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "state", this->state);
dump_field(out, "target_temperature_low", this->target_temperature_low);
dump_field(out, "target_temperature_high", this->target_temperature_high);
}
#endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
void ListEntitiesNumberResponse::dump_to(std::string &out) const { void ListEntitiesNumberResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesNumberResponse"); MessageDumpHelper helper(out, "ListEntitiesNumberResponse");
@@ -1456,7 +1547,7 @@ void SelectCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectCommandRequest"); MessageDumpHelper helper(out, "SelectCommandRequest");
dump_field(out, "key", this->key); dump_field(out, "key", this->key);
out.append(" state: "); out.append(" state: ");
out.append(format_hex_pretty(this->state, this->state_len)); out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n"); out.append("\n");
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
@@ -1473,7 +1564,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
dump_field(out, "icon", this->icon_ref_); dump_field(out, "icon", this->icon_ref_);
#endif #endif
dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "disabled_by_default", this->disabled_by_default);
for (const auto &it : this->tones) { for (const auto &it : *this->tones) {
dump_field(out, "tones", it, 4); dump_field(out, "tones", it, 4);
} }
dump_field(out, "supports_duration", this->supports_duration); dump_field(out, "supports_duration", this->supports_duration);
@@ -1497,7 +1588,9 @@ void SirenCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_state", this->has_state); dump_field(out, "has_state", this->has_state);
dump_field(out, "state", this->state); dump_field(out, "state", this->state);
dump_field(out, "has_tone", this->has_tone); dump_field(out, "has_tone", this->has_tone);
dump_field(out, "tone", this->tone); out.append(" tone: ");
out.append("'").append(this->tone.c_str(), this->tone.size()).append("'");
out.append("\n");
dump_field(out, "has_duration", this->has_duration); dump_field(out, "has_duration", this->has_duration);
dump_field(out, "duration", this->duration); dump_field(out, "duration", this->duration);
dump_field(out, "has_volume", this->has_volume); dump_field(out, "has_volume", this->has_volume);
@@ -1539,7 +1632,9 @@ void LockCommandRequest::dump_to(std::string &out) const {
dump_field(out, "key", this->key); dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::LockCommand>(this->command)); dump_field(out, "command", static_cast<enums::LockCommand>(this->command));
dump_field(out, "has_code", this->has_code); dump_field(out, "has_code", this->has_code);
dump_field(out, "code", this->code); out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
#endif #endif
@@ -1617,7 +1712,9 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_volume", this->has_volume); dump_field(out, "has_volume", this->has_volume);
dump_field(out, "volume", this->volume); dump_field(out, "volume", this->volume);
dump_field(out, "has_media_url", this->has_media_url); dump_field(out, "has_media_url", this->has_media_url);
dump_field(out, "media_url", this->media_url); out.append(" media_url: ");
out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'");
out.append("\n");
dump_field(out, "has_announcement", this->has_announcement); dump_field(out, "has_announcement", this->has_announcement);
dump_field(out, "announcement", this->announcement); dump_field(out, "announcement", this->announcement);
#ifdef USE_DEVICES #ifdef USE_DEVICES
@@ -1847,8 +1944,12 @@ void VoiceAssistantResponse::dump_to(std::string &out) const {
} }
void VoiceAssistantEventData::dump_to(std::string &out) const { void VoiceAssistantEventData::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventData"); MessageDumpHelper helper(out, "VoiceAssistantEventData");
dump_field(out, "name", this->name); out.append(" name: ");
dump_field(out, "value", this->value); out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
out.append(" value: ");
out.append("'").append(this->value.c_str(), this->value.size()).append("'");
out.append("\n");
} }
void VoiceAssistantEventResponse::dump_to(std::string &out) const { void VoiceAssistantEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventResponse"); MessageDumpHelper helper(out, "VoiceAssistantEventResponse");
@@ -1862,28 +1963,34 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
void VoiceAssistantAudio::dump_to(std::string &out) const { void VoiceAssistantAudio::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAudio"); MessageDumpHelper helper(out, "VoiceAssistantAudio");
out.append(" data: "); out.append(" data: ");
if (this->data_ptr_ != nullptr) { out.append(format_hex_pretty(this->data, this->data_len));
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
} else {
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
}
out.append("\n"); out.append("\n");
dump_field(out, "end", this->end); dump_field(out, "end", this->end);
} }
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse"); MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse");
dump_field(out, "event_type", static_cast<enums::VoiceAssistantTimerEvent>(this->event_type)); dump_field(out, "event_type", static_cast<enums::VoiceAssistantTimerEvent>(this->event_type));
dump_field(out, "timer_id", this->timer_id); out.append(" timer_id: ");
dump_field(out, "name", this->name); out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'");
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
dump_field(out, "total_seconds", this->total_seconds); dump_field(out, "total_seconds", this->total_seconds);
dump_field(out, "seconds_left", this->seconds_left); dump_field(out, "seconds_left", this->seconds_left);
dump_field(out, "is_active", this->is_active); dump_field(out, "is_active", this->is_active);
} }
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest"); MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest");
dump_field(out, "media_id", this->media_id); out.append(" media_id: ");
dump_field(out, "text", this->text); out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'");
dump_field(out, "preannounce_media_id", this->preannounce_media_id); out.append("\n");
out.append(" text: ");
out.append("'").append(this->text.c_str(), this->text.size()).append("'");
out.append("\n");
out.append(" preannounce_media_id: ");
out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'");
out.append("\n");
dump_field(out, "start_conversation", this->start_conversation); dump_field(out, "start_conversation", this->start_conversation);
} }
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); } void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
@@ -1897,15 +2004,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
} }
void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const { void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord"); MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
dump_field(out, "id", this->id); out.append(" id: ");
dump_field(out, "wake_word", this->wake_word); out.append("'").append(this->id.c_str(), this->id.size()).append("'");
out.append("\n");
out.append(" wake_word: ");
out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'");
out.append("\n");
for (const auto &it : this->trained_languages) { for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4); dump_field(out, "trained_languages", it, 4);
} }
dump_field(out, "model_type", this->model_type); out.append(" model_type: ");
out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'");
out.append("\n");
dump_field(out, "model_size", this->model_size); dump_field(out, "model_size", this->model_size);
dump_field(out, "model_hash", this->model_hash); out.append(" model_hash: ");
dump_field(out, "url", this->url); out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'");
out.append("\n");
out.append(" url: ");
out.append("'").append(this->url.c_str(), this->url.size()).append("'");
out.append("\n");
} }
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest"); MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
@@ -1964,7 +2081,9 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest"); MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest");
dump_field(out, "key", this->key); dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::AlarmControlPanelStateCommand>(this->command)); dump_field(out, "command", static_cast<enums::AlarmControlPanelStateCommand>(this->command));
dump_field(out, "code", this->code); out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
#endif #endif
@@ -2001,7 +2120,9 @@ void TextStateResponse::dump_to(std::string &out) const {
void TextCommandRequest::dump_to(std::string &out) const { void TextCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextCommandRequest"); MessageDumpHelper helper(out, "TextCommandRequest");
dump_field(out, "key", this->key); dump_field(out, "key", this->key);
dump_field(out, "state", this->state); out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
#endif #endif

View File

@@ -10,6 +10,10 @@
#include "esphome/components/climate/climate_traits.h" #include "esphome/components/climate/climate_traits.h"
#endif #endif
#ifdef USE_WATER_HEATER
#include "esphome/components/water_heater/water_heater.h"
#endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
#include "esphome/components/light/light_traits.h" #include "esphome/components/light/light_traits.h"
#endif #endif

View File

@@ -24,17 +24,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_hello_request(msg); this->on_hello_request(msg);
break; break;
} }
#ifdef USE_API_PASSWORD
case AuthenticationRequest::MESSAGE_TYPE: {
AuthenticationRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
#endif
this->on_authentication_request(msg);
break;
}
#endif
case DisconnectRequest::MESSAGE_TYPE: { case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg; DisconnectRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
@@ -621,6 +610,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_homeassistant_action_response(msg); this->on_homeassistant_action_response(msg);
break; break;
} }
#endif
#ifdef USE_WATER_HEATER
case WaterHeaterCommandRequest::MESSAGE_TYPE: {
WaterHeaterCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str());
#endif
this->on_water_heater_command_request(msg);
break;
}
#endif #endif
default: default:
break; break;
@@ -632,13 +632,6 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
#ifdef USE_API_PASSWORD
void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
if (!this->send_authenticate_response(msg)) {
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();
@@ -830,10 +823,7 @@ void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg)
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements for messages // Check authentication/connection requirements for messages
switch (msg_type) { switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required 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 DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required case PingRequest::MESSAGE_TYPE: // No setup required
break; // Skip all checks for these messages break; // Skip all checks for these messages

View File

@@ -26,10 +26,6 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_hello_request(const HelloRequest &value){}; virtual void on_hello_request(const HelloRequest &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){};
virtual void on_ping_request(const PingRequest &value){}; virtual void on_ping_request(const PingRequest &value){};
@@ -91,6 +87,10 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_climate_command_request(const ClimateCommandRequest &value){}; virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif #endif
#ifdef USE_WATER_HEATER
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
#endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){}; virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif #endif
@@ -224,9 +224,6 @@ 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;
#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;
@@ -353,9 +350,6 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
protected: protected:
void on_hello_request(const HelloRequest &msg) override; void on_hello_request(const HelloRequest &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;

View File

@@ -224,38 +224,6 @@ void APIServer::dump_config() {
#endif #endif
} }
#ifdef USE_API_PASSWORD
bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
// depend only on input password length
const char *a = this->password_.c_str();
uint32_t len_a = this->password_.length();
const char *b = reinterpret_cast<const char *>(password_data);
uint32_t len_b = password_len;
// disable optimization with volatile
volatile uint32_t length = len_b;
volatile const char *left = nullptr;
volatile const char *right = b;
uint8_t result = 0;
if (len_a == length) {
left = *((volatile const char **) &a);
result = 0;
}
if (len_a != length) {
left = b;
result = 1;
}
for (size_t i = 0; i < length; i++) {
result |= *left++ ^ *right++; // NOLINT
}
return result == 0;
}
#endif
void APIServer::handle_disconnect(APIConnection *conn) {} void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for controller update dispatch // Macro for controller update dispatch
@@ -335,6 +303,10 @@ API_DISPATCH_UPDATE(valve::Valve, valve)
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
#endif #endif
#ifdef USE_WATER_HEATER
API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
// Event is a special case - unlike other entities with simple state fields, // Event is a special case - unlike other entities with simple state fields,
// events store their state in a member accessed via obj->get_last_event_type() // events store their state in a member accessed via obj->get_last_event_type()
@@ -373,10 +345,6 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI;
void APIServer::set_port(uint16_t port) { this->port_ = port; } void APIServer::set_port(uint16_t port) { this->port_ = port; }
#ifdef USE_API_PASSWORD
void APIServer::set_password(const std::string &password) { this->password_ = password; }
#endif
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
@@ -390,7 +358,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon
this->action_response_callbacks_.push_back({call_id, std::move(callback)}); this->action_response_callbacks_.push_back({call_id, std::move(callback)});
} }
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) { void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) { for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) { if (it->call_id == call_id) {
auto callback = std::move(it->callback); auto callback = std::move(it->callback);
@@ -402,7 +370,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
} }
} }
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message, void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) { const uint8_t *response_data, size_t response_data_len) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) { for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) { if (it->call_id == call_id) {
@@ -674,7 +642,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
} }
} }
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) { void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) {
for (auto &call : this->active_action_calls_) { for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) { if (call.action_call_id == action_call_id) {
call.connection->send_execute_service_response(call.client_call_id, success, error_message); call.connection->send_execute_service_response(call.client_call_id, success, error_message);
@@ -684,7 +652,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons
ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id); ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id);
} }
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) { const uint8_t *response_data, size_t response_data_len) {
for (auto &call : this->active_action_calls_) { for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) { if (call.action_call_id == action_call_id) {

View File

@@ -59,10 +59,6 @@ class APIServer : public Component,
#endif #endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override; void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
#endif
#ifdef USE_API_PASSWORD
bool check_password(const uint8_t *password_data, size_t password_len) const;
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);
@@ -133,6 +129,9 @@ class APIServer : public Component,
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
#ifdef USE_WATER_HEATER
void on_water_heater_update(water_heater::WaterHeater *obj) override;
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_action(const HomeassistantActionRequest &call); void send_homeassistant_action(const HomeassistantActionRequest &call);
@@ -140,10 +139,10 @@ class APIServer : public Component,
// Action response handling // Action response handling
using ActionResponseCallback = std::function<void(const class ActionResponse &)>; using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback); void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message); void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message, void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
const uint8_t *response_data, size_t response_data_len); size_t response_data_len);
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES #endif // USE_API_HOMEASSISTANT_SERVICES
@@ -162,9 +161,9 @@ class APIServer : public Component,
void unregister_active_action_call(uint32_t action_call_id); void unregister_active_action_call(uint32_t action_call_id);
void unregister_active_action_calls_for_connection(APIConnection *conn); void unregister_active_action_calls_for_connection(APIConnection *conn);
// Send response for a specific action call (uses action_call_id, sends client_call_id in response) // Send response for a specific action call (uses action_call_id, sends client_call_id in response)
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message); void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len); const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -253,9 +252,6 @@ class APIServer : public Component,
// Vectors and strings (12 bytes each on 32-bit) // Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_; std::vector<std::unique_ptr<APIConnection>> clients_;
#ifdef USE_API_PASSWORD
std::string password_;
#endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
std::vector<HomeAssistantStateSubscription> state_subs_; std::vector<HomeAssistantStateSubscription> state_subs_;

View File

@@ -16,7 +16,7 @@ with warnings.catch_warnings():
import contextlib import contextlib
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ from esphome.const import CONF_KEY, CONF_PORT, __version__
from esphome.core import CORE from esphome.core import CORE
from . import CONF_ENCRYPTION from . import CONF_ENCRYPTION
@@ -35,7 +35,6 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
conf = config["api"] conf = config["api"]
name = config["esphome"]["name"] name = config["esphome"]["name"]
port: int = int(conf[CONF_PORT]) port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: str | None = None noise_psk: str | None = None
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)): if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
noise_psk = key noise_psk = key
@@ -50,7 +49,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
cli = APIClient( cli = APIClient(
addresses[0], # Primary address for compatibility addresses[0], # Primary address for compatibility
port, port,
password, "", # Password auth removed in 2026.1.0
client_info=f"ESPHome Logs {__version__}", client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk, noise_psk=noise_psk,
addresses=addresses, # Pass all addresses for automatic retry addresses=addresses, # Pass all addresses for automatic retry

View File

@@ -67,10 +67,10 @@ template<typename... Ts> class TemplatableKeyValuePair {
// the callback is invoked synchronously while the message is on the stack). // the callback is invoked synchronously while the message is on the stack).
class ActionResponse { class ActionResponse {
public: public:
ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {} ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len) ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
: success_(success), error_message_(error_message) { : success_(success), error_message_(error_message) {
if (data == nullptr || data_len == 0) if (data == nullptr || data_len == 0)
return; return;

View File

@@ -73,6 +73,9 @@ LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMedia
LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel, LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel,
ListEntitiesAlarmControlPanelResponse) ListEntitiesAlarmControlPanelResponse)
#endif #endif
#ifdef USE_WATER_HEATER
LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse)
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse) LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
#endif #endif

View File

@@ -82,6 +82,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
#endif #endif
#ifdef USE_WATER_HEATER
bool on_water_heater(water_heater::WaterHeater *entity) override;
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool on_event(event::Event *entity) override; bool on_event(event::Event *entity) override;
#endif #endif

View File

@@ -334,7 +334,7 @@ class ProtoWriteBuffer {
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
this->encode_uint64(field_id, encode_zigzag64(value), force); this->encode_uint64(field_id, encode_zigzag64(value), force);
} }
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false); void encode_message(uint32_t field_id, const ProtoMessage &value);
std::vector<uint8_t> *get_buffer() const { return buffer_; } std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected: protected:
@@ -795,7 +795,7 @@ class ProtoSize {
}; };
// Implementation of encode_message - must be after ProtoMessage is defined // Implementation of encode_message - must be after ProtoMessage is defined
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) { inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first // Calculate the message size first
@@ -833,9 +833,6 @@ class ProtoService {
virtual bool is_authenticated() = 0; virtual bool is_authenticated() = 0;
virtual bool is_connection_setup() = 0; virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0; virtual void on_fatal_error() = 0;
#ifdef USE_API_PASSWORD
virtual void on_unauthenticated_access() = 0;
#endif
virtual void on_no_setup_connection() = 0; virtual void on_no_setup_connection() = 0;
/** /**
* Create a buffer with a reserved size. * Create a buffer with a reserved size.
@@ -873,20 +870,7 @@ class ProtoService {
return true; return true;
} }
inline bool check_authenticated_() { inline bool check_authenticated_() { return this->check_connection_setup_(); }
#ifdef USE_API_PASSWORD
if (!this->check_connection_setup_()) {
return false;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return false;
}
return true;
#else
return this->check_connection_setup_();
#endif
}
}; };
} // namespace esphome::api } // namespace esphome::api

View File

@@ -60,6 +60,9 @@ INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer)
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel) INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel)
#endif #endif
#ifdef USE_WATER_HEATER
INITIAL_STATE_HANDLER(water_heater, water_heater::WaterHeater)
#endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
INITIAL_STATE_HANDLER(update, update::UpdateEntity) INITIAL_STATE_HANDLER(update, update::UpdateEntity)
#endif #endif

View File

@@ -76,6 +76,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
#endif #endif
#ifdef USE_WATER_HEATER
bool on_water_heater(water_heater::WaterHeater *entity) override;
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; }; bool on_event(event::Event *event) override { return true; };
#endif #endif

View File

@@ -255,7 +255,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
bool return_response = std::get<1>(args); bool return_response = std::get<1>(args);
if (!return_response) { if (!return_response) {
// Client doesn't want response data, just send success/error // Client doesn't want response data, just send success/error
this->parent_->send_action_response(call_id, success, error_message); this->parent_->send_action_response(call_id, success, StringRef(error_message));
return; return;
} }
} }
@@ -265,12 +265,12 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
json::JsonBuilder builder; json::JsonBuilder builder;
this->json_builder_(x..., builder.root()); this->json_builder_(x..., builder.root());
std::string json_str = builder.serialize(); std::string json_str = builder.serialize();
this->parent_->send_action_response(call_id, success, error_message, this->parent_->send_action_response(call_id, success, StringRef(error_message),
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size()); reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
return; return;
} }
#endif #endif
this->parent_->send_action_response(call_id, success, error_message); this->parent_->send_action_response(call_id, success, StringRef(error_message));
} }
protected: protected:

View File

@@ -0,0 +1,14 @@
import esphome.codegen as cg
CODEOWNERS = ["@jasstrong", "@ximex", "@freekode"]
aqi_ns = cg.esphome_ns.namespace("aqi")
AQICalculatorType = aqi_ns.enum("AQICalculatorType")
CONF_AQI = "aqi"
CONF_CALCULATION_TYPE = "calculation_type"
AQI_CALCULATION_TYPE = {
"CAQI": AQICalculatorType.CAQI_TYPE,
"AQI": AQICalculatorType.AQI_TYPE,
}

View File

@@ -2,13 +2,11 @@
#include <cstdint> #include <cstdint>
namespace esphome { namespace esphome::aqi {
namespace hm3301 {
class AbstractAQICalculator { class AbstractAQICalculator {
public: public:
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
}; };
} // namespace hm3301 } // namespace esphome::aqi
} // namespace esphome

View File

@@ -1,10 +1,11 @@
#pragma once #pragma once
#include <climits> #include <climits>
#include "abstract_aqi_calculator.h" #include "abstract_aqi_calculator.h"
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf // https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
namespace esphome { namespace esphome::aqi {
namespace hm3301 {
class AQICalculator : public AbstractAQICalculator { class AQICalculator : public AbstractAQICalculator {
public: public:
@@ -28,6 +29,9 @@ class AQICalculator : public AbstractAQICalculator {
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
int grid_index = get_grid_index_(value, array); int grid_index = get_grid_index_(value, array);
if (grid_index == -1) {
return -1;
}
int aqi_lo = index_grid_[grid_index][0]; int aqi_lo = index_grid_[grid_index][0];
int aqi_hi = index_grid_[grid_index][1]; int aqi_hi = index_grid_[grid_index][1];
int conc_lo = array[grid_index][0]; int conc_lo = array[grid_index][0];
@@ -46,5 +50,4 @@ class AQICalculator : public AbstractAQICalculator {
} }
}; };
} // namespace hm3301 } // namespace esphome::aqi
} // namespace esphome

View File

@@ -3,8 +3,7 @@
#include "caqi_calculator.h" #include "caqi_calculator.h"
#include "aqi_calculator.h" #include "aqi_calculator.h"
namespace esphome { namespace esphome::aqi {
namespace hm3301 {
enum AQICalculatorType { CAQI_TYPE = 0, AQI_TYPE = 1 }; enum AQICalculatorType { CAQI_TYPE = 0, AQI_TYPE = 1 };
@@ -12,18 +11,17 @@ class AQICalculatorFactory {
public: public:
AbstractAQICalculator *get_calculator(AQICalculatorType type) { AbstractAQICalculator *get_calculator(AQICalculatorType type) {
if (type == 0) { if (type == 0) {
return caqi_calculator_; return &this->caqi_calculator_;
} else if (type == 1) { } else if (type == 1) {
return aqi_calculator_; return &this->aqi_calculator_;
} }
return nullptr; return nullptr;
} }
protected: protected:
CAQICalculator *caqi_calculator_ = new CAQICalculator(); CAQICalculator caqi_calculator_;
AQICalculator *aqi_calculator_ = new AQICalculator(); AQICalculator aqi_calculator_;
}; };
} // namespace hm3301 } // namespace esphome::aqi
} // namespace esphome

View File

@@ -3,8 +3,7 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "abstract_aqi_calculator.h" #include "abstract_aqi_calculator.h"
namespace esphome { namespace esphome::aqi {
namespace hm3301 {
class CAQICalculator : public AbstractAQICalculator { class CAQICalculator : public AbstractAQICalculator {
public: public:
@@ -48,5 +47,4 @@ class CAQICalculator : public AbstractAQICalculator {
} }
}; };
} // namespace hm3301 } // namespace esphome::aqi
} // namespace esphome

View File

@@ -1,6 +1,6 @@
#include "audio_reader.h" #include "audio_reader.h"
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include "audio.h" #include "audio.h"
#include "audio_transfer_buffer.h" #include "audio_transfer_buffer.h"

View File

@@ -1,12 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, climate from esphome.components import climate
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import CONF_HEAT_MODE, CONF_TEMPERATURE_SOURCE
CONF_HEAT_MODE,
CONF_RECEIVE_TIMEOUT,
CONF_TEMPERATURE_SOURCE,
CONF_TIME_ID,
)
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
@@ -38,22 +33,6 @@ CONFIG_SCHEMA = (
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(
# TODO: remove compat layer.
{
cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid(
"The 'ble_client_id' option has been removed. Please migrate "
"to the new `bedjet_id` option in the `bedjet` component.\n"
"See https://esphome.io/components/climate/bedjet/"
),
cv.Optional(CONF_TIME_ID): cv.invalid(
"The 'time_id' option has been moved to the `bedjet` component."
),
cv.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid(
"The 'receive_timeout' option has been moved to the `bedjet` component."
),
}
)
.extend(BEDJET_CLIENT_SCHEMA) .extend(BEDJET_CLIENT_SCHEMA)
) )

View File

@@ -20,16 +20,6 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_ILLUMINANCE, device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
) )
.extend(
{
cv.Optional("resolution"): cv.invalid(
"The 'resolution' option has been removed. The optimal value is now dynamically calculated."
),
cv.Optional("measurement_duration"): cv.invalid(
"The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated."
),
}
)
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x23)) .extend(i2c.i2c_device_schema(0x23))
) )

View File

@@ -7,8 +7,12 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h" #include "esphome/components/ble_client/ble_client.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars)
static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64;
namespace esphome::ble_client { namespace esphome::ble_client {
// placeholder class for static TAG . // placeholder class for static TAG .
@@ -151,7 +155,10 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false; return false;
} }
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str()); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
#endif
esp_err_t err = esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len, esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE); const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);

View File

@@ -50,6 +50,7 @@ TYPES = [
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(cg.Component),
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component), cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,

View File

@@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
AUTO_LOAD = ["bme68x_bsec2"] AUTO_LOAD = ["bme68x_bsec2"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
MULTI_CONF = True
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c") bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_( BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(

View File

@@ -0,0 +1,36 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MAC_ADDRESS
CODEOWNERS = ["@nagyrobi"]
DEPENDENCIES = ["esp32_ble_tracker"]
BLE_DEVICE_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA
bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer")
BTHomeMiThermometer = bthome_mithermometer_ns.class_(
"BTHomeMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
)
def bthome_mithermometer_base_schema(extra_schema=None):
if extra_schema is None:
extra_schema = {}
return (
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
}
)
.extend(BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
.extend(extra_schema)
)
async def setup_bthome_mithermometer(var, config):
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))

View File

@@ -0,0 +1,298 @@
#include "bthome_ble.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <array>
#ifdef USE_ESP32
namespace esphome {
namespace bthome_mithermometer {
static const char *const TAG = "bthome_mithermometer";
static std::string format_mac_address(uint64_t address) {
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) {
mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF;
}
char buffer[MAC_ADDRESS_SIZE * 3];
format_mac_addr_upper(mac.data(), buffer);
return buffer;
}
static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) {
switch (obj_type) {
case 0x00: // packet id
case 0x01: // battery
case 0x09: // count (uint8)
case 0x0F: // generic boolean
case 0x10: // power (bool)
case 0x11: // opening
case 0x15: // battery low
case 0x16: // battery charging
case 0x17: // carbon monoxide
case 0x18: // cold
case 0x19: // connectivity
case 0x1A: // door
case 0x1B: // garage door
case 0x1C: // gas
case 0x1D: // heat
case 0x1E: // light
case 0x1F: // lock
case 0x20: // moisture
case 0x21: // motion
case 0x22: // moving
case 0x23: // occupancy
case 0x24: // plug
case 0x25: // presence
case 0x26: // problem
case 0x27: // running
case 0x28: // safety
case 0x29: // smoke
case 0x2A: // sound
case 0x2B: // tamper
case 0x2C: // vibration
case 0x2D: // water leak
case 0x2E: // humidity (uint8)
case 0x2F: // moisture (uint8)
case 0x46: // UV index
case 0x57: // temperature (sint8)
case 0x58: // temperature (0.35C step)
case 0x59: // count (sint8)
case 0x60: // channel
value_length = 1;
return true;
case 0x02: // temperature (0.01C)
case 0x03: // humidity
case 0x06: // mass (kg)
case 0x07: // mass (lb)
case 0x08: // dewpoint
case 0x0C: // voltage (mV)
case 0x0D: // pm2.5
case 0x0E: // pm10
case 0x12: // CO2
case 0x13: // TVOC
case 0x14: // moisture
case 0x3D: // count (uint16)
case 0x3F: // rotation
case 0x40: // distance (mm)
case 0x41: // distance (m)
case 0x43: // current (A)
case 0x44: // speed
case 0x45: // temperature (0.1C)
case 0x47: // volume (L)
case 0x48: // volume (mL)
case 0x49: // volume flow rate
case 0x4A: // voltage (0.1V)
case 0x51: // acceleration
case 0x52: // gyroscope
case 0x56: // conductivity
case 0x5A: // count (sint16)
case 0x5D: // current (sint16)
case 0x5E: // direction
case 0x5F: // precipitation
case 0x61: // rotational speed
case 0xF0: // button event
value_length = 2;
return true;
case 0x04: // pressure
case 0x05: // illuminance
case 0x0A: // energy
case 0x0B: // power
case 0x42: // duration
case 0x4B: // gas (uint24)
case 0xF2: // firmware version (uint24)
value_length = 3;
return true;
case 0x3E: // count (uint32)
case 0x4C: // gas (uint32)
case 0x4D: // energy (uint32)
case 0x4E: // volume (uint32)
case 0x4F: // water (uint32)
case 0x50: // timestamp
case 0x55: // volume storage
case 0x5B: // count (sint32)
case 0x5C: // power (sint32)
case 0x62: // speed (sint32)
case 0x63: // acceleration (sint32)
case 0xF1: // firmware version (uint32)
value_length = 4;
return true;
default:
return false;
}
}
void BTHomeMiThermometer::dump_config() {
ESP_LOGCONFIG(TAG, "BTHome MiThermometer");
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(this->address_).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Signal Strength", this->signal_strength_);
}
bool BTHomeMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
bool matched = false;
for (auto &service_data : device.get_service_datas()) {
if (this->handle_service_data_(service_data, device)) {
matched = true;
}
}
if (matched && this->signal_strength_ != nullptr) {
this->signal_strength_->publish_state(device.get_rssi());
}
return matched;
}
bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
const esp32_ble_tracker::ESPBTDevice &device) {
if (!service_data.uuid.contains(0xD2, 0xFC)) {
return false;
}
const auto &data = service_data.data;
if (data.size() < 2) {
ESP_LOGVV(TAG, "BTHome data too short: %zu", data.size());
return false;
}
const uint8_t adv_info = data[0];
const bool is_encrypted = adv_info & 0x01;
const bool mac_included = adv_info & 0x02;
const bool is_trigger_based = adv_info & 0x04;
const uint8_t version = (adv_info >> 5) & 0x07;
if (version != 0x02) {
ESP_LOGVV(TAG, "Unsupported BTHome version %u", version);
return false;
}
if (is_encrypted) {
ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str().c_str());
return false;
}
size_t payload_index = 1;
uint64_t source_address = device.address_uint64();
if (mac_included) {
if (data.size() < 7) {
ESP_LOGVV(TAG, "BTHome payload missing MAC address");
return false;
}
source_address = 0;
for (int i = 5; i >= 0; i--) {
source_address = (source_address << 8) | data[1 + i];
}
payload_index = 7;
}
if (source_address != this->address_) {
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(source_address).c_str());
return false;
}
if (payload_index >= data.size()) {
ESP_LOGVV(TAG, "BTHome payload empty after header");
return false;
}
bool reported = false;
size_t offset = payload_index;
uint8_t last_type = 0;
while (offset < data.size()) {
const uint8_t obj_type = data[offset++];
size_t value_length = 0;
bool has_length_byte = obj_type == 0x53; // text objects include explicit length
if (has_length_byte) {
if (offset >= data.size()) {
break;
}
value_length = data[offset++];
} else {
if (!get_bthome_value_length(obj_type, value_length)) {
ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type);
break;
}
}
if (value_length == 0) {
break;
}
if (offset + value_length > data.size()) {
ESP_LOGVV(TAG, "BTHome object length exceeds payload");
break;
}
const uint8_t *value = &data[offset];
offset += value_length;
if (obj_type < last_type) {
ESP_LOGVV(TAG, "BTHome objects not in ascending order");
}
last_type = obj_type;
switch (obj_type) {
case 0x00: { // packet id
const uint8_t packet_id = value[0];
if (this->last_packet_id_.has_value() && *this->last_packet_id_ == packet_id) {
return reported;
}
this->last_packet_id_ = packet_id;
break;
}
case 0x01: { // battery percentage
if (this->battery_level_ != nullptr) {
this->battery_level_->publish_state(value[0]);
reported = true;
}
break;
}
case 0x0C: { // battery voltage (mV)
if (this->battery_voltage_ != nullptr) {
const uint16_t raw = encode_uint16(value[1], value[0]);
this->battery_voltage_->publish_state(raw * 0.001f);
reported = true;
}
break;
}
case 0x02: { // temperature
if (this->temperature_ != nullptr) {
const int16_t raw = encode_uint16(value[1], value[0]);
this->temperature_->publish_state(raw * 0.01f);
reported = true;
}
break;
}
case 0x03: { // humidity
if (this->humidity_ != nullptr) {
const uint16_t raw = encode_uint16(value[1], value[0]);
this->humidity_->publish_state(raw * 0.01f);
reported = true;
}
break;
}
default:
break;
}
}
if (reported) {
ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str().c_str());
}
return reported;
}
} // namespace bthome_mithermometer
} // namespace esphome
#endif

View File

@@ -0,0 +1,44 @@
#pragma once
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include <cstdint>
#ifdef USE_ESP32
namespace esphome {
namespace bthome_mithermometer {
class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public:
void set_address(uint64_t address) { this->address_ = address; }
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { this->battery_voltage_ = battery_voltage; }
void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; }
void dump_config() override;
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
protected:
bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
const esp32_ble_tracker::ESPBTDevice &device);
uint64_t address_{0};
optional<uint8_t> last_packet_id_{};
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *humidity_{nullptr};
sensor::Sensor *battery_level_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *signal_strength_{nullptr};
};
} // namespace bthome_mithermometer
} // namespace esphome
#endif

View File

@@ -0,0 +1,88 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_LEVEL,
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_ID,
CONF_SIGNAL_STRENGTH,
CONF_TEMPERATURE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_DECIBEL_MILLIWATT,
UNIT_PERCENT,
UNIT_VOLT,
)
from . import bthome_mithermometer_base_schema, setup_bthome_mithermometer
CODEOWNERS = ["@nagyrobi"]
DEPENDENCIES = ["esp32_ble_tracker"]
CONFIG_SCHEMA = bthome_mithermometer_base_schema(
{
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
icon="mdi:battery-plus",
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await setup_bthome_mithermometer(var, config)
if temp_sens := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temp_sens)
cg.add(var.set_temperature(sens))
if humi_sens := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humi_sens)
cg.add(var.set_humidity(sens))
if batl_sens := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(batl_sens)
cg.add(var.set_battery_level(sens))
if batv_sens := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(batv_sens)
cg.add(var.set_battery_voltage(sens))
if sgnl_sens := config.get(CONF_SIGNAL_STRENGTH):
sens = await sensor.new_sensor(sgnl_sens)
cg.add(var.set_signal_strength(sens))

View File

@@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass {
*/ */
virtual void press_action() = 0; virtual void press_action() = 0;
CallbackManager<void()> press_callback_{}; LazyCallbackManager<void()> press_callback_{};
}; };
} // namespace esphome::button } // namespace esphome::button

View File

@@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__)
def AUTO_LOAD() -> list[str]: def AUTO_LOAD() -> list[str]:
auto_load = ["web_server_base", "ota.web_server"] auto_load = ["web_server_base", "ota.web_server"]
if CORE.using_esp_idf: if CORE.is_esp32:
auto_load.append("socket") auto_load.append("socket")
return auto_load return auto_load
@@ -97,10 +97,6 @@ async def to_code(config):
cg.add_define("USE_CAPTIVE_PORTAL") cg.add_define("USE_CAPTIVE_PORTAL")
if CORE.using_arduino: if CORE.using_arduino:
if CORE.is_esp32:
cg.add_library("ESP32 Async UDP", None)
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
if CORE.is_esp8266: if CORE.is_esp8266:
cg.add_library("DNSServer", None) cg.add_library("DNSServer", None)
if CORE.is_libretiny: if CORE.is_libretiny:
@@ -110,6 +106,9 @@ async def to_code(config):
# Only compile the ESP-IDF DNS server when using ESP-IDF framework # Only compile the ESP-IDF DNS server when using ESP-IDF framework
FILTER_SOURCE_FILES = filter_source_files_from_platform( FILTER_SOURCE_FILES = filter_source_files_from_platform(
{ {
"dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF}, "dns_server_esp32_idf.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
} }
) )

View File

@@ -65,22 +65,15 @@ void CaptivePortal::start() {
this->base_->init(); this->base_->init();
if (!this->initialized_) { if (!this->initialized_) {
this->base_->add_handler(this); this->base_->add_handler(this);
#ifdef USE_ESP32
// Enable LRU socket purging to handle captive portal detection probe bursts
// OS captive portal detection makes many simultaneous HTTP requests which can
// exhaust sockets. LRU purging automatically closes oldest idle connections.
this->base_->get_server()->set_lru_purge_enable(true);
#endif
} }
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
#ifdef USE_ESP_IDF #if defined(USE_ESP32)
// Create DNS server instance for ESP-IDF // Create DNS server instance for ESP-IDF
this->dns_server_ = make_unique<DNSServer>(); this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->start(ip); this->dns_server_->start(ip);
#endif #elif defined(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);
this->dns_server_->start(53, ESPHOME_F("*"), ip); this->dns_server_->start(53, ESPHOME_F("*"), ip);

View File

@@ -2,11 +2,10 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#ifdef USE_CAPTIVE_PORTAL #ifdef USE_CAPTIVE_PORTAL
#include <memory> #include <memory>
#ifdef USE_ARDUINO #if defined(USE_ESP32)
#include <DNSServer.h>
#endif
#ifdef USE_ESP_IDF
#include "dns_server_esp32_idf.h" #include "dns_server_esp32_idf.h"
#elif defined(USE_ARDUINO)
#include <DNSServer.h>
#endif #endif
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@@ -23,15 +22,14 @@ class CaptivePortal : public AsyncWebHandler, public Component {
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void loop() override { void loop() override {
#ifdef USE_ARDUINO #if defined(USE_ESP32)
if (this->dns_server_ != nullptr) {
this->dns_server_->processNextRequest();
}
#endif
#ifdef USE_ESP_IDF
if (this->dns_server_ != nullptr) { if (this->dns_server_ != nullptr) {
this->dns_server_->process_next_request(); this->dns_server_->process_next_request();
} }
#elif defined(USE_ARDUINO)
if (this->dns_server_ != nullptr) {
this->dns_server_->processNextRequest();
}
#endif #endif
} }
float get_setup_priority() const override; float get_setup_priority() const override;
@@ -40,10 +38,6 @@ class CaptivePortal : public AsyncWebHandler, public Component {
void end() { void end() {
this->active_ = false; this->active_ = false;
this->disable_loop(); // Stop processing DNS requests this->disable_loop(); // Stop processing DNS requests
#ifdef USE_ESP32
// Disable LRU socket purging now that captive portal is done
this->base_->get_server()->set_lru_purge_enable(false);
#endif
this->base_->deinit(); this->base_->deinit();
if (this->dns_server_ != nullptr) { if (this->dns_server_ != nullptr) {
this->dns_server_->stop(); this->dns_server_->stop();
@@ -68,7 +62,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};
#if defined(USE_ARDUINO) || defined(USE_ESP_IDF) #if defined(USE_ARDUINO) || defined(USE_ESP32)
std::unique_ptr<DNSServer> dns_server_{nullptr}; std::unique_ptr<DNSServer> dns_server_{nullptr};
#endif #endif
}; };

View File

@@ -1,5 +1,5 @@
#include "dns_server_esp32_idf.h" #include "dns_server_esp32_idf.h"
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
@@ -47,7 +47,10 @@ struct DNSAnswer {
void DNSServer::start(const network::IPAddress &ip) { void DNSServer::start(const network::IPAddress &ip) {
this->server_ip_ = ip; this->server_ip_ = ip;
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str()); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf));
#endif
// Create loop-monitored UDP socket // Create loop-monitored UDP socket
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP); this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
@@ -202,4 +205,4 @@ void DNSServer::process_next_request() {
} // namespace esphome::captive_portal } // namespace esphome::captive_portal
#endif // USE_ESP_IDF #endif // USE_ESP32

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
#ifdef USE_ESP_IDF #ifdef USE_ESP32
#include <memory> #include <memory>
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@@ -24,4 +24,4 @@ class DNSServer {
} // namespace esphome::captive_portal } // namespace esphome::captive_portal
#endif // USE_ESP_IDF #endif // USE_ESP32

View File

@@ -1,9 +1,17 @@
from esphome import automation from esphome import automation, pins
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import spi from esphome.components import spi
from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME from esphome.const import (
CONF_CHANNEL,
CONF_DATA,
CONF_FREQUENCY,
CONF_ID,
CONF_WAIT_TIME,
)
from esphome.core import ID
CODEOWNERS = ["@lygris", "@gabest11"] CODEOWNERS = ["@lygris", "@gabest11"]
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@@ -29,7 +37,6 @@ CONF_MANCHESTER = "manchester"
CONF_NUM_PREAMBLE = "num_preamble" CONF_NUM_PREAMBLE = "num_preamble"
CONF_SYNC1 = "sync1" CONF_SYNC1 = "sync1"
CONF_SYNC0 = "sync0" CONF_SYNC0 = "sync0"
CONF_PKTLEN = "pktlen"
CONF_MAGN_TARGET = "magn_target" CONF_MAGN_TARGET = "magn_target"
CONF_MAX_LNA_GAIN = "max_lna_gain" CONF_MAX_LNA_GAIN = "max_lna_gain"
CONF_MAX_DVGA_GAIN = "max_dvga_gain" CONF_MAX_DVGA_GAIN = "max_dvga_gain"
@@ -41,6 +48,12 @@ CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
CONF_FREEZE = "freeze" CONF_FREEZE = "freeze"
CONF_HYST_LEVEL = "hyst_level" CONF_HYST_LEVEL = "hyst_level"
# Packet mode config keys
CONF_PACKET_MODE = "packet_mode"
CONF_PACKET_LENGTH = "packet_length"
CONF_WHITENING = "whitening"
CONF_GDO0_PIN = "gdo0_pin"
# Enums # Enums
SyncMode = ns.enum("SyncMode", True) SyncMode = ns.enum("SyncMode", True)
SYNC_MODE = { SYNC_MODE = {
@@ -147,45 +160,89 @@ HYST_LEVEL = {
"High": HystLevel.HYST_LEVEL_HIGH, "High": HystLevel.HYST_LEVEL_HIGH,
} }
# Config key -> Validator mapping # Optional settings to generate setter calls for
CONFIG_MAP = { CONFIG_MAP = {
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum(
CONF_DC_BLOCKING_FILTER: cv.boolean, RX_ATTENUATION, upper=False
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)), ),
CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean,
CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All(
CONF_CHANNEL: cv.uint8_t, cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)
CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)), ),
CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)), cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All(
CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), cv.frequency, cv.float_range(min=25000, max=788000)
CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), ),
CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All(
CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean, cv.frequency, cv.float_range(min=58000, max=812000)
CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False), ),
CONF_MANCHESTER: cv.boolean, cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t,
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All(
CONF_SYNC1: cv.hex_uint8_t, cv.frequency, cv.float_range(min=25000, max=405000)
CONF_SYNC0: cv.hex_uint8_t, ),
CONF_PKTLEN: cv.uint8_t, cv.Optional(CONF_FSK_DEVIATION): cv.All(
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), cv.frequency, cv.float_range(min=1500, max=381000)
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), ),
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8),
CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7), cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000),
CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False), cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False),
CONF_LNA_PRIORITY: cv.boolean, cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean,
CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False), cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum(
CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False), MODULATION, upper=False
CONF_FREEZE: cv.enum(FREEZE, upper=False), ),
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), cv.Optional(CONF_MANCHESTER, default=False): cv.boolean,
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7),
cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t,
cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t,
cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False),
cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum(
MAX_LNA_GAIN, upper=False
),
cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False),
cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7),
cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum(
CARRIER_SENSE_REL_THR, upper=False
),
cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean,
cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum(
FILTER_LENGTH_FSK_MSK, upper=False
),
cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum(
FILTER_LENGTH_ASK_OOK, upper=False
),
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
cv.Optional(CONF_WHITENING, default=False): cv.boolean,
} }
CONFIG_SCHEMA = (
cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)}) def _validate_packet_mode(config):
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) if config.get(CONF_PACKET_MODE, False):
if CONF_GDO0_PIN not in config:
raise cv.Invalid("gdo0_pin is required when packet_mode is enabled")
if CONF_PACKET_LENGTH not in config:
raise cv.Invalid("packet_length is required when packet_mode is enabled")
if config[CONF_PACKET_LENGTH] > 64:
raise cv.Invalid("packet_length must be <= 64 (FIFO size)")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CC1101Component),
cv.Optional(CONF_GDO0_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
}
)
.extend(CONFIG_MAP)
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema(cs_pin_required=True)) .extend(spi.spi_device_schema(cs_pin_required=True)),
_validate_packet_mode,
) )
@@ -194,16 +251,34 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)
for key in CONFIG_MAP: for opt in CONFIG_MAP:
key = opt.schema
if key in config: if key in config:
cg.add(getattr(var, f"set_{key}")(config[key])) cg.add(getattr(var, f"set_{key}")(config[key]))
if CONF_GDO0_PIN in config:
gdo0_pin = await cg.gpio_pin_expression(config[CONF_GDO0_PIN])
cg.add(var.set_gdo0_pin(gdo0_pin))
if CONF_ON_PACKET in config:
await automation.build_automation(
var.get_packet_trigger(),
[
(cg.std_vector.template(cg.uint8), "x"),
(cg.float_, "rssi"),
(cg.uint8, "lqi"),
],
config[CONF_ON_PACKET],
)
# Actions # Actions
BeginTxAction = ns.class_("BeginTxAction", automation.Action) BeginTxAction = ns.class_("BeginTxAction", automation.Action)
BeginRxAction = ns.class_("BeginRxAction", automation.Action) BeginRxAction = ns.class_("BeginRxAction", automation.Action)
ResetAction = ns.class_("ResetAction", automation.Action) ResetAction = ns.class_("ResetAction", automation.Action)
SetIdleAction = ns.class_("SetIdleAction", automation.Action) SetIdleAction = ns.class_("SetIdleAction", automation.Action)
SendPacketAction = ns.class_(
"SendPacketAction", automation.Action, cg.Parented.template(CC1101Component)
)
CC1101_ACTION_SCHEMA = cv.Schema( CC1101_ACTION_SCHEMA = cv.Schema(
maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)}) maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)})
@@ -218,3 +293,42 @@ async def cc1101_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
return var return var
def validate_raw_data(value):
if isinstance(value, str):
return value.encode("utf-8")
if isinstance(value, list):
return cv.Schema([cv.hex_uint8_t])(value)
raise cv.Invalid(
"data must either be a string wrapped in quotes or a list of bytes"
)
SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(CC1101Component),
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
},
key=CONF_DATA,
)
@automation.register_action(
"cc1101.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA
)
async def send_packet_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
data = config[CONF_DATA]
if isinstance(data, bytes):
data = list(data)
if cg.is_template(data):
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_data_template(templ))
else:
# Generate static array in flash to avoid RAM copy
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
cg.add(var.set_data_static(arr, len(data)))
return var

View File

@@ -98,25 +98,8 @@ CC1101Component::CC1101Component() {
this->state_.LENGTH_CONFIG = 2; this->state_.LENGTH_CONFIG = 2;
this->state_.FS_AUTOCAL = 1; this->state_.FS_AUTOCAL = 1;
// Default Settings
this->set_frequency(433920);
this->set_if_frequency(153);
this->set_filter_bandwidth(203);
this->set_channel(0);
this->set_channel_spacing(200);
this->set_symbol_rate(5000);
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
this->set_carrier_sense_above_threshold(true);
this->set_modulation_type(Modulation::MODULATION_ASK_OOK);
this->set_magn_target(MagnTarget::MAGN_TARGET_42DB);
this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT);
this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3);
this->set_lna_priority(false);
this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES);
// CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence) // CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence)
memset(this->pa_table_, 0, sizeof(this->pa_table_)); memset(this->pa_table_, 0, sizeof(this->pa_table_));
this->set_output_power(10.0f);
} }
void CC1101Component::setup() { void CC1101Component::setup() {
@@ -143,6 +126,11 @@ void CC1101Component::setup() {
return; return;
} }
// Setup GDO0 pin if configured
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->setup();
}
this->initialized_ = true; this->initialized_ = true;
for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) { for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) {
@@ -151,8 +139,70 @@ void CC1101Component::setup() {
} }
this->write_(static_cast<Register>(i)); this->write_(static_cast<Register>(i));
} }
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); this->set_output_power(this->output_power_requested_);
this->strobe_(Command::RX); this->strobe_(Command::RX);
// Defer pin mode setup until after all components have completed setup()
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
if (this->gdo0_pin_ != nullptr) {
this->defer([this]() { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); });
}
}
void CC1101Component::loop() {
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr ||
!this->gdo0_pin_->digital_read()) {
return;
}
// Read state
this->read_(Register::RXBYTES);
uint8_t rx_bytes = this->state_.NUM_RXBYTES;
bool overflow = this->state_.RXFIFO_OVERFLOW;
if (overflow || rx_bytes == 0) {
ESP_LOGW(TAG, "RX FIFO overflow, flushing");
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return;
}
// Read packet
uint8_t payload_length, expected_rx;
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->read_(Register::FIFO, &payload_length, 1);
expected_rx = payload_length + 1;
} else {
payload_length = this->state_.PKTLEN;
expected_rx = payload_length;
}
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return;
}
this->packet_.resize(payload_length);
this->read_(Register::FIFO, this->packet_.data(), payload_length);
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
this->read_(Register::RSSI);
this->read_(Register::LQI);
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
if (this->state_.CRC_EN == 0 || crc_ok) {
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
}
// Return to rx
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
} }
void CC1101Component::dump_config() { void CC1101Component::dump_config() {
@@ -177,9 +227,12 @@ void CC1101Component::dump_config() {
} }
void CC1101Component::begin_tx() { void CC1101Component::begin_tx() {
// Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX // Ensure Packet Format is 3 (Async Serial)
this->write_(Register::PKTCTRL0, 0x32); this->write_(Register::PKTCTRL0, 0x32);
ESP_LOGV(TAG, "Beginning TX sequence"); ESP_LOGV(TAG, "Beginning TX sequence");
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT);
}
this->strobe_(Command::TX); this->strobe_(Command::TX);
if (!this->wait_for_state_(State::TX, 50)) { if (!this->wait_for_state_(State::TX, 50)) {
ESP_LOGW(TAG, "Timed out waiting for TX state!"); ESP_LOGW(TAG, "Timed out waiting for TX state!");
@@ -188,6 +241,9 @@ void CC1101Component::begin_tx() {
void CC1101Component::begin_rx() { void CC1101Component::begin_rx() {
ESP_LOGV(TAG, "Beginning RX sequence"); ESP_LOGV(TAG, "Beginning RX sequence");
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
}
this->strobe_(Command::RX); this->strobe_(Command::RX);
} }
@@ -201,20 +257,6 @@ void CC1101Component::set_idle() {
this->enter_idle_(); this->enter_idle_();
} }
void CC1101Component::set_gdo0_config(uint8_t value) {
this->state_.GDO0_CFG = value;
if (this->initialized_) {
this->write_(Register::IOCFG0);
}
}
void CC1101Component::set_gdo2_config(uint8_t value) {
this->state_.GDO2_CFG = value;
if (this->initialized_) {
this->write_(Register::IOCFG2);
}
}
bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
uint32_t start = millis(); uint32_t start = millis();
while (millis() - start < timeout_ms) { while (millis() - start < timeout_ms) {
@@ -282,6 +324,33 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) {
this->disable(); this->disable();
} }
CC1101Error CC1101Component::transmit_packet(const std::vector<uint8_t> &packet) {
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO)) {
return CC1101Error::PARAMS;
}
// Write packet
this->enter_idle_();
this->strobe_(Command::FTX);
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->write_(Register::FIFO, static_cast<uint8_t>(packet.size()));
}
this->write_(Register::FIFO, packet.data(), packet.size());
this->strobe_(Command::TX);
if (!this->wait_for_state_(State::IDLE, 1000)) {
ESP_LOGW(TAG, "TX timeout");
this->enter_idle_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return CC1101Error::TIMEOUT;
}
// Return to rx
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return CC1101Error::NONE;
}
// Setters // Setters
void CC1101Component::set_output_power(float value) { void CC1101Component::set_output_power(float value) {
this->output_power_requested_ = value; this->output_power_requested_ = value;
@@ -428,6 +497,7 @@ void CC1101Component::set_modulation_type(Modulation value) {
this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0; this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0;
if (this->initialized_) { if (this->initialized_) {
this->enter_idle_(); this->enter_idle_();
this->set_output_power(this->output_power_requested_);
this->write_(Register::MDMCFG2); this->write_(Register::MDMCFG2);
this->write_(Register::FREND0); this->write_(Register::FREND0);
this->strobe_(Command::RX); this->strobe_(Command::RX);
@@ -462,13 +532,6 @@ void CC1101Component::set_sync0(uint8_t value) {
} }
} }
void CC1101Component::set_pktlen(uint8_t value) {
this->state_.PKTLEN = value;
if (this->initialized_) {
this->write_(Register::PKTLEN);
}
}
void CC1101Component::set_magn_target(MagnTarget value) { void CC1101Component::set_magn_target(MagnTarget value) {
this->state_.MAGN_TARGET = static_cast<uint8_t>(value); this->state_.MAGN_TARGET = static_cast<uint8_t>(value);
if (this->initialized_) { if (this->initialized_) {
@@ -546,4 +609,53 @@ void CC1101Component::set_hyst_level(HystLevel value) {
} }
} }
void CC1101Component::set_packet_mode(bool value) {
this->state_.PKT_FORMAT =
static_cast<uint8_t>(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL);
if (value) {
// Configure GDO0 for FIFO status (asserts on RX FIFO threshold or end of packet)
this->state_.GDO0_CFG = 0x01;
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
this->state_.FIFO_THR = 15;
// Don't append status bytes to FIFO - we read from registers instead
this->state_.APPEND_STATUS = 0;
} else {
// Configure GDO0 for serial data (async serial mode)
this->state_.GDO0_CFG = 0x0D;
}
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
this->write_(Register::PKTCTRL1);
this->write_(Register::IOCFG0);
this->write_(Register::FIFOTHR);
}
}
void CC1101Component::set_packet_length(uint8_t value) {
if (value == 0) {
this->state_.LENGTH_CONFIG = static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE);
} else {
this->state_.LENGTH_CONFIG = static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_FIXED);
this->state_.PKTLEN = value;
}
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
this->write_(Register::PKTLEN);
}
}
void CC1101Component::set_crc_enable(bool value) {
this->state_.CRC_EN = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
}
}
void CC1101Component::set_whitening(bool value) {
this->state_.WHITE_DATA = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
}
}
} // namespace esphome::cc1101 } // namespace esphome::cc1101

View File

@@ -5,9 +5,12 @@
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "cc1101defs.h" #include "cc1101defs.h"
#include <vector>
namespace esphome::cc1101 { namespace esphome::cc1101 {
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW };
class CC1101Component : public Component, class CC1101Component : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> { spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
@@ -15,6 +18,7 @@ class CC1101Component : public Component,
CC1101Component(); CC1101Component();
void setup() override; void setup() override;
void loop() override;
void dump_config() override; void dump_config() override;
// Actions // Actions
@@ -24,8 +28,7 @@ class CC1101Component : public Component,
void set_idle(); void set_idle();
// GDO Pin Configuration // GDO Pin Configuration
void set_gdo0_config(uint8_t value); void set_gdo0_pin(InternalGPIOPin *pin) { this->gdo0_pin_ = pin; }
void set_gdo2_config(uint8_t value);
// Configuration Setters // Configuration Setters
void set_output_power(float value); void set_output_power(float value);
@@ -48,7 +51,6 @@ class CC1101Component : public Component,
void set_num_preamble(uint8_t value); void set_num_preamble(uint8_t value);
void set_sync1(uint8_t value); void set_sync1(uint8_t value);
void set_sync0(uint8_t value); void set_sync0(uint8_t value);
void set_pktlen(uint8_t value);
// AGC settings // AGC settings
void set_magn_target(MagnTarget value); void set_magn_target(MagnTarget value);
@@ -63,6 +65,16 @@ class CC1101Component : public Component,
void set_wait_time(WaitTime value); void set_wait_time(WaitTime value);
void set_hyst_level(HystLevel value); void set_hyst_level(HystLevel value);
// Packet mode settings
void set_packet_mode(bool value);
void set_packet_length(uint8_t value);
void set_crc_enable(bool value);
void set_whitening(bool value);
// Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
Trigger<std::vector<uint8_t>, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
protected: protected:
uint16_t chip_id_{0}; uint16_t chip_id_{0};
bool initialized_{false}; bool initialized_{false};
@@ -73,6 +85,13 @@ class CC1101Component : public Component,
CC1101State state_; CC1101State state_;
// GDO pin for packet reception
InternalGPIOPin *gdo0_pin_{nullptr};
// Packet handling
Trigger<std::vector<uint8_t>, float, uint8_t> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, uint8_t>()};
std::vector<uint8_t> packet_;
// Low-level Helpers // Low-level Helpers
uint8_t strobe_(Command cmd); uint8_t strobe_(Command cmd);
void write_(Register reg); void write_(Register reg);
@@ -107,4 +126,28 @@ template<typename... Ts> class SetIdleAction : public Action<Ts...>, public Pare
void play(const Ts &...x) override { this->parent_->set_idle(); } void play(const Ts &...x) override { this->parent_->set_idle(); }
}; };
template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<CC1101Component> {
public:
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->data_func_ = func; }
void set_data_static(const uint8_t *data, size_t len) {
this->data_static_ = data;
this->data_static_len_ = len;
}
void play(const Ts &...x) override {
if (this->data_func_) {
auto data = this->data_func_(x...);
this->parent_->transmit_packet(data);
} else if (this->data_static_ != nullptr) {
std::vector<uint8_t> data(this->data_static_, this->data_static_ + this->data_static_len_);
this->parent_->transmit_packet(data);
}
}
protected:
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
const uint8_t *data_static_{nullptr};
size_t data_static_len_{0};
};
} // namespace esphome::cc1101 } // namespace esphome::cc1101

View File

@@ -6,6 +6,12 @@ namespace esphome::cc1101 {
static constexpr float XTAL_FREQUENCY = 26000000; static constexpr float XTAL_FREQUENCY = 26000000;
static constexpr float RSSI_OFFSET = 74.0f;
static constexpr float RSSI_STEP = 0.5f;
static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80;
static constexpr uint8_t STATUS_LQI_MASK = 0x7F;
static constexpr uint8_t BUS_BURST = 0x40; static constexpr uint8_t BUS_BURST = 0x40;
static constexpr uint8_t BUS_READ = 0x80; static constexpr uint8_t BUS_READ = 0x80;
static constexpr uint8_t BUS_WRITE = 0x00; static constexpr uint8_t BUS_WRITE = 0x00;
@@ -134,6 +140,10 @@ enum class SyncMode : uint8_t {
SYNC_MODE_15_16, SYNC_MODE_15_16,
SYNC_MODE_16_16, SYNC_MODE_16_16,
SYNC_MODE_30_32, SYNC_MODE_30_32,
SYNC_MODE_NONE_CS,
SYNC_MODE_15_16_CS,
SYNC_MODE_16_16_CS,
SYNC_MODE_30_32_CS,
}; };
enum class Modulation : uint8_t { enum class Modulation : uint8_t {
@@ -218,6 +228,19 @@ enum class HystLevel : uint8_t {
HYST_LEVEL_HIGH, HYST_LEVEL_HIGH,
}; };
enum class PacketFormat : uint8_t {
PACKET_FORMAT_FIFO,
PACKET_FORMAT_SYNC_SERIAL,
PACKET_FORMAT_RANDOM_TX,
PACKET_FORMAT_ASYNC_SERIAL,
};
enum class LengthConfig : uint8_t {
LENGTH_CONFIG_FIXED,
LENGTH_CONFIG_VARIABLE,
LENGTH_CONFIG_INFINITE,
};
struct __attribute__((packed)) CC1101State { struct __attribute__((packed)) CC1101State {
// Byte array accessors for bulk SPI transfers // Byte array accessors for bulk SPI transfers
uint8_t *regs() { return reinterpret_cast<uint8_t *>(this); } uint8_t *regs() { return reinterpret_cast<uint8_t *>(this); }

View File

@@ -128,7 +128,9 @@ void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); } void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); } size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_);
}
void CH422GGPIOPin::set_flags(gpio::Flags flags) { void CH422GGPIOPin::set_flags(gpio::Flags flags) {
flags_ = flags; flags_ = flags;
this->parent_->pin_mode(this->pin_, flags); this->parent_->pin_mode(this->pin_, flags);

View File

@@ -50,7 +50,7 @@ class CH422GGPIOPin : public GPIOPin {
void pin_mode(gpio::Flags flags) override; void pin_mode(gpio::Flags flags) override;
bool digital_read() override; bool digital_read() override;
void digital_write(bool value) override; void digital_write(bool value) override;
std::string dump_summary() const override; size_t dump_summary(char *buffer, size_t len) const override;
void set_parent(CH422GComponent *parent) { parent_ = parent; } void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; } void set_pin(uint8_t pin) { pin_ = pin; }

View File

@@ -117,9 +117,7 @@ CONF_MIN_HUMIDITY = "min_humidity"
CONF_MAX_HUMIDITY = "max_humidity" CONF_MAX_HUMIDITY = "max_humidity"
CONF_TARGET_HUMIDITY = "target_humidity" CONF_TARGET_HUMIDITY = "target_humidity"
visual_temperature = cv.float_with_unit( visual_temperature = cv.float_with_unit("visual_temperature", "(°|(° ?)?[CKF])?")
"visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?"
)
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema( VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(

View File

@@ -2,6 +2,7 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h" #include "esphome/core/controller_registry.h"
#include "esphome/core/macros.h" #include "esphome/core/macros.h"
#include <strings.h>
namespace esphome::climate { namespace esphome::climate {
@@ -190,24 +191,30 @@ ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
} }
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) { ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) {
return this->set_fan_mode(custom_fan_mode, strlen(custom_fan_mode));
}
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
return this->set_fan_mode(fan_mode.data(), fan_mode.size());
}
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode, size_t len) {
// Check if it's a standard enum mode first // Check if it's a standard enum mode first
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) { for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) { if (strncasecmp(custom_fan_mode, mode_entry.str, len) == 0 && mode_entry.str[len] == '\0') {
return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value)); return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
} }
} }
// Find the matching pointer from parent climate device // Find the matching pointer from parent climate device
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) { if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode, len)) {
this->custom_fan_mode_ = mode_ptr; this->custom_fan_mode_ = mode_ptr;
this->fan_mode_.reset(); this->fan_mode_.reset();
return *this; return *this;
} }
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode); ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %.*s", this->parent_->get_name().c_str(), (int) len, custom_fan_mode);
return *this; return *this;
} }
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); }
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) { ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
if (fan_mode.has_value()) { if (fan_mode.has_value()) {
this->set_fan_mode(fan_mode.value()); this->set_fan_mode(fan_mode.value());
@@ -222,24 +229,30 @@ ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
} }
ClimateCall &ClimateCall::set_preset(const char *custom_preset) { ClimateCall &ClimateCall::set_preset(const char *custom_preset) {
return this->set_preset(custom_preset, strlen(custom_preset));
}
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
return this->set_preset(preset.data(), preset.size());
}
ClimateCall &ClimateCall::set_preset(const char *custom_preset, size_t len) {
// Check if it's a standard enum preset first // Check if it's a standard enum preset first
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) { for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
if (str_equals_case_insensitive(custom_preset, preset_entry.str)) { if (strncasecmp(custom_preset, preset_entry.str, len) == 0 && preset_entry.str[len] == '\0') {
return this->set_preset(static_cast<ClimatePreset>(preset_entry.value)); return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
} }
} }
// Find the matching pointer from parent climate device // Find the matching pointer from parent climate device
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) { if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset, len)) {
this->custom_preset_ = preset_ptr; this->custom_preset_ = preset_ptr;
this->preset_.reset(); this->preset_.reset();
return *this; return *this;
} }
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset); ESP_LOGW(TAG, "'%s' - Unrecognized preset %.*s", this->parent_->get_name().c_str(), (int) len, custom_preset);
return *this; return *this;
} }
ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); }
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) { ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
if (preset.has_value()) { if (preset.has_value()) {
this->set_preset(preset.value()); this->set_preset(preset.value());
@@ -356,7 +369,7 @@ optional<ClimateDeviceRestoreState> Climate::restore_state_() {
} }
void Climate::save_state_() { void Climate::save_state_() {
#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ #if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
!defined(CLANG_TIDY) !defined(CLANG_TIDY)
#pragma GCC diagnostic ignored "-Wclass-memaccess" #pragma GCC diagnostic ignored "-Wclass-memaccess"
#define TEMP_IGNORE_MEMACCESS #define TEMP_IGNORE_MEMACCESS
@@ -688,11 +701,19 @@ bool Climate::set_custom_preset_(const char *preset) {
void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; } void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; }
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) { const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
return this->get_traits().find_custom_fan_mode_(custom_fan_mode); return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
}
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) {
return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len);
} }
const char *Climate::find_custom_preset_(const char *custom_preset) { const char *Climate::find_custom_preset_(const char *custom_preset) {
return this->get_traits().find_custom_preset_(custom_preset); return this->find_custom_preset_(custom_preset, strlen(custom_preset));
}
const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) {
return this->get_traits().find_custom_preset_(custom_preset, len);
} }
void Climate::dump_traits_(const char *tag) { void Climate::dump_traits_(const char *tag) {

View File

@@ -78,6 +78,8 @@ class ClimateCall {
ClimateCall &set_fan_mode(optional<std::string> fan_mode); ClimateCall &set_fan_mode(optional<std::string> fan_mode);
/// Set the custom fan mode of the climate device. /// Set the custom fan mode of the climate device.
ClimateCall &set_fan_mode(const char *custom_fan_mode); ClimateCall &set_fan_mode(const char *custom_fan_mode);
/// Set the custom fan mode of the climate device (zero-copy API path).
ClimateCall &set_fan_mode(const char *custom_fan_mode, size_t len);
/// Set the swing mode of the climate device. /// Set the swing mode of the climate device.
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode); ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
/// Set the swing mode of the climate device. /// Set the swing mode of the climate device.
@@ -94,6 +96,8 @@ class ClimateCall {
ClimateCall &set_preset(optional<std::string> preset); ClimateCall &set_preset(optional<std::string> preset);
/// Set the custom preset of the climate device. /// Set the custom preset of the climate device.
ClimateCall &set_preset(const char *custom_preset); ClimateCall &set_preset(const char *custom_preset);
/// Set the custom preset of the climate device (zero-copy API path).
ClimateCall &set_preset(const char *custom_preset, size_t len);
void perform(); void perform();
@@ -290,9 +294,11 @@ class Climate : public EntityBase {
/// Find and return the matching custom fan mode pointer from traits, or nullptr if not found. /// Find and return the matching custom fan mode pointer from traits, or nullptr if not found.
const char *find_custom_fan_mode_(const char *custom_fan_mode); const char *find_custom_fan_mode_(const char *custom_fan_mode);
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len);
/// Find and return the matching custom preset pointer from traits, or nullptr if not found. /// Find and return the matching custom preset pointer from traits, or nullptr if not found.
const char *find_custom_preset_(const char *custom_preset); const char *find_custom_preset_(const char *custom_preset);
const char *find_custom_preset_(const char *custom_preset, size_t len);
/** Get the default traits of this climate device. /** Get the default traits of this climate device.
* *
@@ -320,8 +326,8 @@ class Climate : public EntityBase {
void dump_traits_(const char *tag); void dump_traits_(const char *tag);
CallbackManager<void(Climate &)> state_callback_{}; LazyCallbackManager<void(Climate &)> state_callback_{};
CallbackManager<void(ClimateCall &)> control_callback_{}; LazyCallbackManager<void(ClimateCall &)> control_callback_{};
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
#ifdef USE_CLIMATE_VISUAL_OVERRIDES #ifdef USE_CLIMATE_VISUAL_OVERRIDES
float visual_min_temperature_override_{NAN}; float visual_min_temperature_override_{NAN};

View File

@@ -20,18 +20,22 @@ using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimateP
// Lightweight linear search for small vectors (1-20 items) of const char* pointers // Lightweight linear search for small vectors (1-20 items) of const char* pointers
// Avoids std::find template overhead // Avoids std::find template overhead
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) { inline bool vector_contains(const std::vector<const char *> &vec, const char *value, size_t len) {
for (const char *item : vec) { for (const char *item : vec) {
if (strcmp(item, value) == 0) if (strncmp(item, value, len) == 0 && item[len] == '\0')
return true; return true;
} }
return false; return false;
} }
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
return vector_contains(vec, value, strlen(value));
}
// Find and return matching pointer from vector, or nullptr if not found // Find and return matching pointer from vector, or nullptr if not found
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) { inline const char *vector_find(const std::vector<const char *> &vec, const char *value, size_t len) {
for (const char *item : vec) { for (const char *item : vec) {
if (strcmp(item, value) == 0) if (strncmp(item, value, len) == 0 && item[len] == '\0')
return item; return item;
} }
return nullptr; return nullptr;
@@ -257,13 +261,19 @@ class ClimateTraits {
/// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found /// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found
/// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead /// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead
const char *find_custom_fan_mode_(const char *custom_fan_mode) const { const char *find_custom_fan_mode_(const char *custom_fan_mode) const {
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode); return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
}
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const {
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len);
} }
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
/// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead /// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead
const char *find_custom_preset_(const char *custom_preset) const { const char *find_custom_preset_(const char *custom_preset) const {
return vector_find(this->supported_custom_presets_, custom_preset); return this->find_custom_preset_(custom_preset, strlen(custom_preset));
}
const char *find_custom_preset_(const char *custom_preset, size_t len) const {
return vector_find(this->supported_custom_presets_, custom_preset, len);
} }
uint32_t feature_flags_{0}; uint32_t feature_flags_{0};

View File

@@ -7,9 +7,11 @@ BYTE_ORDER_LITTLE = "little_endian"
BYTE_ORDER_BIG = "big_endian" BYTE_ORDER_BIG = "big_endian"
CONF_COLOR_DEPTH = "color_depth" CONF_COLOR_DEPTH = "color_depth"
CONF_CRC_ENABLE = "crc_enable"
CONF_DRAW_ROUNDING = "draw_rounding" CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLED = "enabled" CONF_ENABLED = "enabled"
CONF_IGNORE_NOT_FOUND = "ignore_not_found" CONF_IGNORE_NOT_FOUND = "ignore_not_found"
CONF_ON_PACKET = "on_packet"
CONF_ON_RECEIVE = "on_receive" CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers" CONF_REQUEST_HEADERS = "request_headers"

View File

@@ -7,7 +7,7 @@ namespace copy {
static const char *const TAG = "copy.select"; static const char *const TAG = "copy.select";
void CopySelect::setup() { void CopySelect::setup() {
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); }); source_->add_on_state_callback([this](size_t index) { this->publish_state(index); });
traits.set_options(source_->traits.get_options()); traits.set_options(source_->traits.get_options());

View File

@@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
optional<CoverRestoreState> restore_state_(); optional<CoverRestoreState> restore_state_();
CallbackManager<void()> state_callback_{}; LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
}; };

View File

@@ -1,11 +1,13 @@
#include "cse7766.h" #include "cse7766.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace cse7766 { namespace cse7766 {
static const char *const TAG = "cse7766"; static const char *const TAG = "cse7766";
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
void CSE7766Component::loop() { void CSE7766Component::loop() {
const uint32_t now = App.get_loop_component_start_time(); const uint32_t now = App.get_loop_component_start_time();
@@ -70,8 +72,8 @@ bool CSE7766Component::check_byte_() {
void CSE7766Component::parse_data_() { void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{ {
std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_)); char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)];
ESP_LOGVV(TAG, "Raw data: %s", s.c_str()); ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_)));
} }
#endif #endif

View File

@@ -51,7 +51,7 @@ void DallasTemperatureSensor::update() {
} }
float tempc = this->get_temp_c_(); float tempc = this->get_temp_c_();
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc); ESP_LOGD(TAG, "'%s': Got Temperature=%f°C", this->get_name().c_str(), tempc);
this->publish_state(tempc); this->publish_state(tempc);
}); });
} }

View File

@@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase {
#endif #endif
protected: protected:
CallbackManager<void()> state_callback_; LazyCallbackManager<void()> state_callback_;
#ifdef USE_TIME #ifdef USE_TIME
time::RealTimeClock *rtc_; time::RealTimeClock *rtc_;

View File

@@ -200,7 +200,7 @@ void DebugComponent::get_device_info_(std::string &device_info) {
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
ESP_LOGD(TAG, "Framework: Arduino"); ESP_LOGD(TAG, "Framework: Arduino");
device_info += "Arduino"; device_info += "Arduino";
#elif defined(USE_ESP_IDF) #elif defined(USE_ESP32)
ESP_LOGD(TAG, "Framework: ESP-IDF"); ESP_LOGD(TAG, "Framework: ESP-IDF");
device_info += "ESP-IDF"; device_info += "ESP-IDF";
#else #else

View File

@@ -1,4 +1,4 @@
from esphome import automation, pins from esphome import automation, core, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32, time from esphome.components import esp32, time
from esphome.components.esp32 import ( from esphome.components.esp32 import (
@@ -23,16 +23,20 @@ from esphome.const import (
CONF_MINUTE, CONF_MINUTE,
CONF_MODE, CONF_MODE,
CONF_NUMBER, CONF_NUMBER,
CONF_PIN,
CONF_PINS, CONF_PINS,
CONF_RUN_DURATION, CONF_RUN_DURATION,
CONF_SECOND, CONF_SECOND,
CONF_SLEEP_DURATION, CONF_SLEEP_DURATION,
CONF_TIME_ID, CONF_TIME_ID,
CONF_WAKEUP_PIN, CONF_WAKEUP_PIN,
PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PlatformFramework, PlatformFramework,
) )
from esphome.core import CORE
from esphome.types import ConfigType
WAKEUP_PINS = { WAKEUP_PINS = {
VARIANT_ESP32: [ VARIANT_ESP32: [
@@ -113,7 +117,7 @@ WAKEUP_PINS = {
} }
def validate_pin_number(value): def validate_pin_number_esp32(value: ConfigType) -> ConfigType:
valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32]) valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32])
if value[CONF_NUMBER] not in valid_pins: if value[CONF_NUMBER] not in valid_pins:
raise cv.Invalid( raise cv.Invalid(
@@ -122,6 +126,51 @@ def validate_pin_number(value):
return value return value
def validate_pin_number(value: ConfigType) -> ConfigType:
if not CORE.is_esp32:
return value
return validate_pin_number_esp32(value)
def validate_wakeup_pin(
value: ConfigType | list[ConfigType],
) -> list[ConfigType]:
if not isinstance(value, list):
processed_pins: list[ConfigType] = [{CONF_PIN: value}]
else:
processed_pins = list(value)
for i, pin_config in enumerate(processed_pins):
# now validate each item
validated_pin = WAKEUP_PIN_SCHEMA(pin_config)
validate_pin_number(validated_pin[CONF_PIN])
processed_pins[i] = validated_pin
return processed_pins
def validate_config(config: ConfigType) -> ConfigType:
# right now only BK72XX supports the list format for wakeup pins
if CORE.is_bk72xx:
if CONF_WAKEUP_PIN_MODE in config:
wakeup_pins = config.get(CONF_WAKEUP_PIN, [])
if len(wakeup_pins) > 1:
raise cv.Invalid(
"You need to remove the global wakeup_pin_mode and define it per pin"
)
if wakeup_pins:
wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE)
elif (
isinstance(config.get(CONF_WAKEUP_PIN), list)
and len(config[CONF_WAKEUP_PIN]) > 1
):
raise cv.Invalid(
"Your platform does not support providing multiple entries in wakeup_pin"
)
return config
def _validate_ex1_wakeup_mode(value): def _validate_ex1_wakeup_mode(value):
if value == "ALL_LOW": if value == "ALL_LOW":
esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value) esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value)
@@ -141,6 +190,15 @@ def _validate_ex1_wakeup_mode(value):
return value return value
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
if not CORE.is_bk72xx:
return value
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
return value
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
@@ -186,6 +244,13 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema(
} }
) )
WAKEUP_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.enum(WAKEUP_PIN_MODES, upper=True),
}
)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@@ -194,14 +259,15 @@ CONFIG_SCHEMA = cv.All(
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA), cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
), ),
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, cv.Optional(CONF_SLEEP_DURATION): cv.All(
cv.Optional(CONF_WAKEUP_PIN): cv.All( cv.positive_time_period_milliseconds,
cv.only_on_esp32, _validate_sleep_duration,
pins.internal_gpio_input_pin_schema,
validate_pin_number,
), ),
cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin,
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(
cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]),
cv.enum(WAKEUP_PIN_MODES),
upper=True,
), ),
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
cv.only_on_esp32, cv.only_on_esp32,
@@ -212,7 +278,8 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Required(CONF_PINS): cv.ensure_list( cv.Required(CONF_PINS): cv.ensure_list(
pins.internal_gpio_input_pin_schema, validate_pin_number pins.internal_gpio_input_pin_schema,
validate_pin_number_esp32,
), ),
cv.Required(CONF_MODE): cv.All( cv.Required(CONF_MODE): cv.All(
cv.enum(EXT1_WAKEUP_MODES, upper=True), cv.enum(EXT1_WAKEUP_MODES, upper=True),
@@ -238,7 +305,8 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
validate_config,
) )
@@ -249,8 +317,21 @@ async def to_code(config):
if CONF_SLEEP_DURATION in config: if CONF_SLEEP_DURATION in config:
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION])) cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
if CONF_WAKEUP_PIN in config: if CONF_WAKEUP_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN]) pins_as_list = config.get(CONF_WAKEUP_PIN, [])
cg.add(var.set_wakeup_pin(pin)) if CORE.is_bk72xx:
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
for item in pins_as_list:
cg.add(
var.add_wakeup_pin(
await cg.gpio_pin_expression(item[CONF_PIN]),
item.get(
CONF_WAKEUP_PIN_MODE, WakeupPinMode.WAKEUP_PIN_MODE_IGNORE
),
)
)
else:
pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN])
cg.add(var.set_wakeup_pin(pin))
if CONF_WAKEUP_PIN_MODE in config: if CONF_WAKEUP_PIN_MODE in config:
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE])) cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
if CONF_RUN_DURATION in config: if CONF_RUN_DURATION in config:
@@ -305,7 +386,10 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
cv.positive_time_period_milliseconds cv.All(
cv.positive_time_period_milliseconds,
_validate_sleep_duration,
)
), ),
# Only on ESP32 due to how long the RTC on ESP8266 can stay asleep # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
cv.Exclusive(CONF_UNTIL, "time"): cv.All( cv.Exclusive(CONF_UNTIL, "time"): cv.All(
@@ -363,5 +447,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF, PlatformFramework.ESP32_IDF,
}, },
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"deep_sleep_bk72xx.cpp": {PlatformFramework.BK72XX_ARDUINO},
} }
) )

View File

@@ -0,0 +1,64 @@
#ifdef USE_BK72XX
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep.bk72xx";
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {
for (const WakeUpPinItem &item : this->wakeup_pins_) {
LOG_PIN(" Wakeup Pin: ", item.wakeup_pin);
}
}
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const {
return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr &&
!this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin)));
}
bool DeepSleepComponent::prepare_to_sleep_() {
if (wakeup_pins_.size() > 0) {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (pin_prevents_sleep_(item)) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
return false;
}
}
}
return true;
}
void DeepSleepComponent::deep_sleep_() {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (item.wakeup_pin_mode == WAKEUP_PIN_MODE_INVERT_WAKEUP) {
if (item.wakeup_level == get_real_pin_state_(*item.wakeup_pin)) {
item.wakeup_level = !item.wakeup_level;
}
}
ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
static_cast<int32_t>(item.wakeup_pin_mode));
}
if (this->sleep_duration_.has_value())
lt_deep_sleep_config_timer((*this->sleep_duration_ / 1000) & 0xFFFFFFFF);
for (WakeUpPinItem &item : this->wakeup_pins_) {
lt_deep_sleep_config_gpio(1 << item.wakeup_pin->get_pin(), item.wakeup_level);
lt_deep_sleep_keep_floating_gpio(1 << item.wakeup_pin->get_pin(), true);
}
lt_deep_sleep_enter();
}
} // namespace esphome::deep_sleep
#endif // USE_BK72XX

View File

@@ -19,7 +19,7 @@
namespace esphome { namespace esphome {
namespace deep_sleep { namespace deep_sleep {
#ifdef USE_ESP32 #if defined(USE_ESP32) || defined(USE_BK72XX)
/** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32 /** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32
* and the scenario occurs that the wakeup pin is already in the wakeup state. * and the scenario occurs that the wakeup pin is already in the wakeup state.
@@ -33,7 +33,17 @@ enum WakeupPinMode {
*/ */
WAKEUP_PIN_MODE_INVERT_WAKEUP, WAKEUP_PIN_MODE_INVERT_WAKEUP,
}; };
#endif
#if defined(USE_BK72XX)
struct WakeUpPinItem {
InternalGPIOPin *wakeup_pin;
WakeupPinMode wakeup_pin_mode;
bool wakeup_level;
};
#endif // USE_BK72XX
#ifdef USE_ESP32
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
struct Ext1Wakeup { struct Ext1Wakeup {
uint64_t mask; uint64_t mask;
@@ -75,6 +85,13 @@ class DeepSleepComponent : public Component {
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
#endif // USE_ESP32 #endif // USE_ESP32
#if defined(USE_BK72XX)
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) {
this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()});
}
#endif // USE_BK72XX
#if defined(USE_ESP32) #if defined(USE_ESP32)
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
@@ -114,7 +131,17 @@ class DeepSleepComponent : public Component {
bool prepare_to_sleep_(); bool prepare_to_sleep_();
void deep_sleep_(); void deep_sleep_();
#ifdef USE_BK72XX
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
#endif // USE_BK72XX
optional<uint64_t> sleep_duration_; optional<uint64_t> sleep_duration_;
#ifdef USE_BK72XX
FixedVector<WakeUpPinItem> wakeup_pins_;
#endif // USE_BK72XX
#ifdef USE_ESP32 #ifdef USE_ESP32
InternalGPIOPin *wakeup_pin_; InternalGPIOPin *wakeup_pin_;
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
@@ -124,8 +151,10 @@ class DeepSleepComponent : public Component {
#endif #endif
optional<bool> touch_wakeup_; optional<bool> touch_wakeup_;
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_; optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
#endif // USE_ESP32 #endif // USE_ESP32
optional<uint32_t> run_duration_; optional<uint32_t> run_duration_;
bool next_enter_deep_sleep_{false}; bool next_enter_deep_sleep_{false};
bool prevent_{false}; bool prevent_{false};

View File

@@ -8,17 +8,20 @@ namespace dht {
static const char *const TAG = "dht"; static const char *const TAG = "dht";
void DHT::setup() { void DHT::setup() {
this->pin_->digital_write(true); this->t_pin_->digital_write(true);
this->pin_->setup(); this->t_pin_->setup();
this->pin_->digital_write(true); #ifdef USE_ESP32
this->t_pin_->pin_mode(this->t_pin_->get_flags() | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN);
#endif
this->t_pin_->digital_write(true);
} }
void DHT::dump_config() { void DHT::dump_config() {
ESP_LOGCONFIG(TAG, "DHT:"); ESP_LOGCONFIG(TAG, "DHT:");
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->t_pin_);
ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "", ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "",
this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent"); this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent");
ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->pin_->get_flags() & gpio::FLAG_PULLUP)); ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
@@ -72,21 +75,15 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
int8_t i = 0; int8_t i = 0;
uint8_t data[5] = {0, 0, 0, 0, 0}; uint8_t data[5] = {0, 0, 0, 0, 0};
this->pin_->digital_write(false); #ifndef USE_ESP32
this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_.pin_mode(gpio::FLAG_OUTPUT);
this->pin_->digital_write(false); #endif
this->pin_.digital_write(false);
if (this->model_ == DHT_MODEL_DHT11) { if (this->model_ == DHT_MODEL_DHT11) {
delayMicroseconds(18000); delayMicroseconds(18000);
} else if (this->model_ == DHT_MODEL_SI7021) { } else if (this->model_ == DHT_MODEL_SI7021) {
#ifdef USE_ESP8266
delayMicroseconds(500); delayMicroseconds(500);
this->pin_->digital_write(true);
delayMicroseconds(40);
#else
delayMicroseconds(400);
this->pin_->digital_write(true);
#endif
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
delayMicroseconds(2000); delayMicroseconds(2000);
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) { } else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
@@ -94,7 +91,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
} else { } else {
delayMicroseconds(800); delayMicroseconds(800);
} }
this->pin_->pin_mode(this->pin_->get_flags());
#ifdef USE_ESP32
this->pin_.digital_write(true);
#else
this->pin_.pin_mode(this->t_pin_->get_flags());
#endif
{ {
InterruptLock lock; InterruptLock lock;
@@ -110,7 +112,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
uint32_t start_time = micros(); uint32_t start_time = micros();
// Wait for rising edge // Wait for rising edge
while (!this->pin_->digital_read()) { while (!this->pin_.digital_read()) {
if (micros() - start_time > 90) { if (micros() - start_time > 90) {
if (i < 0) { if (i < 0) {
error_code = 1; // line didn't clear error_code = 1; // line didn't clear
@@ -127,7 +129,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
uint32_t end_time = start_time; uint32_t end_time = start_time;
// Wait for falling edge // Wait for falling edge
while (this->pin_->digital_read()) { while (this->pin_.digital_read()) {
end_time = micros(); end_time = micros();
if (end_time - start_time > 90) { if (end_time - start_time > 90) {
if (i < 0) { if (i < 0) {

View File

@@ -38,7 +38,10 @@ class DHT : public PollingComponent {
*/ */
void set_dht_model(DHTModel model); void set_dht_model(DHTModel model);
void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_pin(InternalGPIOPin *pin) {
this->t_pin_ = pin;
this->pin_ = pin->to_isr();
}
void set_model(DHTModel model) { model_ = model; } void set_model(DHTModel model) { model_ = model; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
@@ -54,7 +57,8 @@ class DHT : public PollingComponent {
protected: protected:
bool read_sensor_(float *temperature, float *humidity, bool report_errors); bool read_sensor_(float *temperature, float *humidity, bool report_errors);
InternalGPIOPin *pin_; InternalGPIOPin *t_pin_;
ISRInternalGPIOPin pin_;
DHTModel model_{DHT_MODEL_AUTO_DETECT}; DHTModel model_{DHT_MODEL_AUTO_DETECT};
bool is_auto_detect_{false}; bool is_auto_detect_{false};
sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr};

View File

@@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() {
if (this->select_var_ != nullptr) { if (this->select_var_ != nullptr) {
this->select_var_->make_call().select_next(true).perform(); this->select_var_->make_call().select_next(true).perform();
this->on_value_();
changed = true; changed = true;
} }
@@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() {
if (this->select_var_ != nullptr) { if (this->select_var_ != nullptr) {
this->select_var_->make_call().select_previous(true).perform(); this->select_var_->make_call().select_previous(true).perform();
this->on_value_();
changed = true; changed = true;
} }

View File

@@ -7,6 +7,9 @@ namespace ee895 {
static const char *const TAG = "ee895"; static const char *const TAG = "ee895";
// Serial number is 16 bytes
static constexpr size_t EE895_SERIAL_NUMBER_SIZE = 16;
static const uint16_t CRC16_ONEWIRE_START = 0xFFFF; static const uint16_t CRC16_ONEWIRE_START = 0xFFFF;
static const uint8_t FUNCTION_CODE_READ = 0x03; static const uint8_t FUNCTION_CODE_READ = 0x03;
static const uint16_t SERIAL_NUMBER = 0x0000; static const uint16_t SERIAL_NUMBER = 0x0000;
@@ -26,7 +29,10 @@ void EE895Component::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str()); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char serial_hex[format_hex_size(EE895_SERIAL_NUMBER_SIZE)];
#endif
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, serial_number + 2, EE895_SERIAL_NUMBER_SIZE));
} }
void EE895Component::dump_config() { void EE895Component::dump_config() {

View File

@@ -7,6 +7,7 @@
namespace esphome::epaper_spi { namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi"; static const char *const TAG = "epaper_spi";
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
static constexpr const char *const EPAPER_STATE_STRINGS[] = { static constexpr const char *const EPAPER_STATE_STRINGS[] = {
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE", "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
@@ -68,8 +69,11 @@ void EPaperBase::data(uint8_t value) {
// The command is the first byte, length is the length of data only in the second byte, followed by the data. // The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...] // [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) { void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
format_hex_pretty(ptr, length, '.', false).c_str()); format_hex_pretty_to(hex_buf, ptr, length, '.'));
#endif
this->dc_pin_->digital_write(false); this->dc_pin_->digital_write(false);
this->enable(); this->enable();

View File

@@ -76,6 +76,12 @@ class EPaperBase : public Display,
return 0; return 0;
} }
void fill(Color color) override { void fill(Color color) override {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
auto pixel_color = color_to_bit(color) ? 0xFF : 0x00; auto pixel_color = color_to_bit(color) ? 0xFF : 0x00;
// We store 8 pixels per byte // We store 8 pixels per byte

View File

@@ -97,6 +97,12 @@ void EPaperSpectraE6::deep_sleep() {
} }
void EPaperSpectraE6::fill(Color color) { void EPaperSpectraE6::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
EPaperBase::fill(color);
return;
}
auto pixel_color = color_to_hex(color); auto pixel_color = color_to_hex(color);
// We store 2 pixels per byte // We store 2 pixels per byte

View File

@@ -4,6 +4,7 @@ import itertools
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
import re
from esphome import yaml_util from esphome import yaml_util
import esphome.codegen as cg import esphome.codegen as cg
@@ -12,6 +13,7 @@ from esphome.const import (
CONF_ADVANCED, CONF_ADVANCED,
CONF_BOARD, CONF_BOARD,
CONF_COMPONENTS, CONF_COMPONENTS,
CONF_DISABLED,
CONF_ESPHOME, CONF_ESPHOME,
CONF_FRAMEWORK, CONF_FRAMEWORK,
CONF_IGNORE_EFUSE_CUSTOM_MAC, CONF_IGNORE_EFUSE_CUSTOM_MAC,
@@ -23,6 +25,7 @@ from esphome.const import (
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_REF, CONF_REF,
CONF_REFRESH, CONF_REFRESH,
CONF_SAFE_MODE,
CONF_SOURCE, CONF_SOURCE,
CONF_TYPE, CONF_TYPE,
CONF_VARIANT, CONF_VARIANT,
@@ -80,7 +83,9 @@ CONF_ASSERTION_LEVEL = "assertion_level"
CONF_COMPILER_OPTIMIZATION = "compiler_optimization" CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features" CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision"
CONF_RELEASE = "release" CONF_RELEASE = "release"
LOG_LEVELS_IDF = [ LOG_LEVELS_IDF = [
@@ -105,6 +110,21 @@ COMPILER_OPTIMIZATIONS = {
"SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE", "SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE",
} }
# ESP32 (original) chip revision options
# Setting minimum revision to 3.0 or higher:
# - Reduces flash size by excluding workaround code for older chip bugs
# - For PSRAM users: disables CONFIG_SPIRAM_CACHE_WORKAROUND, which saves significant
# IRAM by keeping C library functions in ROM instead of recompiling them
# See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/chip_revision.html
ESP32_CHIP_REVISIONS = {
"0.0": "CONFIG_ESP32_REV_MIN_0",
"1.0": "CONFIG_ESP32_REV_MIN_1",
"1.1": "CONFIG_ESP32_REV_MIN_1_1",
"2.0": "CONFIG_ESP32_REV_MIN_2",
"3.0": "CONFIG_ESP32_REV_MIN_3",
"3.1": "CONFIG_ESP32_REV_MIN_3_1",
}
# Socket limit configuration for ESP-IDF # Socket limit configuration for ESP-IDF
# ESP-IDF CONFIG_LWIP_MAX_SOCKETS has range 1-253, default 10 # ESP-IDF CONFIG_LWIP_MAX_SOCKETS has range 1-253, default 10
DEFAULT_MAX_SOCKETS = 10 # ESP-IDF default DEFAULT_MAX_SOCKETS = 10 # ESP-IDF default
@@ -117,8 +137,8 @@ ARDUINO_ALLOWED_VARIANTS = [
] ]
def get_cpu_frequencies(*frequencies): def get_cpu_frequencies(*frequencies: int) -> list[str]:
return [str(x) + "MHZ" for x in frequencies] return [f"{frequency}MHZ" for frequency in frequencies]
CPU_FREQUENCIES = { CPU_FREQUENCIES = {
@@ -135,7 +155,7 @@ CPU_FREQUENCIES = {
} }
# Make sure not missed here if a new variant added. # Make sure not missed here if a new variant added.
assert all(v in CPU_FREQUENCIES for v in VARIANTS) assert all(variant in CPU_FREQUENCIES for variant in VARIANTS)
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values())) FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
@@ -249,10 +269,10 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component( def add_idf_component(
*, *,
name: str, name: str,
repo: str = None, repo: str | None = None,
ref: str = None, ref: str | None = None,
path: str = None, path: str | None = None,
refresh: TimePeriod = None, refresh: TimePeriod | None = None,
components: list[str] | None = None, components: list[str] | None = None,
submodules: list[str] | None = None, submodules: list[str] | None = None,
): ):
@@ -333,7 +353,7 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}" return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
def _is_framework_url(source: str) -> str: def _is_framework_url(source: str) -> bool:
# platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink # platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink
import urllib.parse import urllib.parse
@@ -353,11 +373,12 @@ def _is_framework_url(source: str) -> str:
# The default/recommended arduino framework version # The default/recommended arduino framework version
# - https://github.com/espressif/arduino-esp32/releases # - https://github.com/espressif/arduino-esp32/releases
ARDUINO_FRAMEWORK_VERSION_LOOKUP = { ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(3, 3, 2), "recommended": cv.Version(3, 3, 5),
"latest": cv.Version(3, 3, 4), "latest": cv.Version(3, 3, 5),
"dev": cv.Version(3, 3, 4), "dev": cv.Version(3, 3, 5),
} }
ARDUINO_PLATFORM_VERSION_LOOKUP = { ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"),
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"),
@@ -370,15 +391,33 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(3, 1, 1): cv.Version(53, 3, 11), cv.Version(3, 1, 1): cv.Version(53, 3, 11),
cv.Version(3, 1, 0): cv.Version(53, 3, 10), cv.Version(3, 1, 0): cv.Version(53, 3, 10),
} }
# Maps Arduino framework versions to a compatible ESP-IDF version
# These versions correspond to pioarduino/esp-idf releases
# See: https://github.com/pioarduino/esp-idf/releases
ARDUINO_IDF_VERSION_LOOKUP = {
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
cv.Version(3, 3, 4): cv.Version(5, 5, 1),
cv.Version(3, 3, 3): cv.Version(5, 5, 1),
cv.Version(3, 3, 2): cv.Version(5, 5, 1),
cv.Version(3, 3, 1): cv.Version(5, 5, 1),
cv.Version(3, 3, 0): cv.Version(5, 5, 0),
cv.Version(3, 2, 1): cv.Version(5, 4, 2),
cv.Version(3, 2, 0): cv.Version(5, 4, 2),
cv.Version(3, 1, 3): cv.Version(5, 3, 2),
cv.Version(3, 1, 2): cv.Version(5, 3, 2),
cv.Version(3, 1, 1): cv.Version(5, 3, 1),
cv.Version(3, 1, 0): cv.Version(5, 3, 0),
}
# The default/recommended esp-idf framework version # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(5, 5, 1), "recommended": cv.Version(5, 5, 2),
"latest": cv.Version(5, 5, 1), "latest": cv.Version(5, 5, 2),
"dev": cv.Version(5, 5, 1), "dev": cv.Version(5, 5, 2),
} }
ESP_IDF_PLATFORM_VERSION_LOOKUP = { ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 2): cv.Version(55, 3, 35),
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"), cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"), cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
cv.Version(5, 4, 3): cv.Version(55, 3, 32), cv.Version(5, 4, 3): cv.Version(55, 3, 32),
@@ -395,9 +434,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
# The platform-espressif32 version # The platform-espressif32 version
# - https://github.com/pioarduino/platform-espressif32/releases # - https://github.com/pioarduino/platform-espressif32/releases
PLATFORM_VERSION_LOOKUP = { PLATFORM_VERSION_LOOKUP = {
"recommended": cv.Version(55, 3, 31, "2"), "recommended": cv.Version(55, 3, 35),
"latest": cv.Version(55, 3, 31, "2"), "latest": cv.Version(55, 3, 35),
"dev": cv.Version(55, 3, 31, "2"), "dev": cv.Version(55, 3, 35),
} }
@@ -543,6 +582,16 @@ def final_validate(config):
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC], path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC],
) )
) )
if (
config[CONF_VARIANT] != VARIANT_ESP32
and advanced.get(CONF_MINIMUM_CHIP_REVISION) is not None
):
errs.append(
cv.Invalid(
f"'{CONF_MINIMUM_CHIP_REVISION}' is only supported on {VARIANT_ESP32}",
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION],
)
)
if advanced[CONF_EXECUTE_FROM_PSRAM]: if advanced[CONF_EXECUTE_FROM_PSRAM]:
if config[CONF_VARIANT] != VARIANT_ESP32S3: if config[CONF_VARIANT] != VARIANT_ESP32S3:
errs.append( errs.append(
@@ -570,6 +619,13 @@ def final_validate(config):
path=[CONF_FLASH_SIZE], path=[CONF_FLASH_SIZE],
) )
) )
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
safe_mode_config = full_config.get(CONF_SAFE_MODE)
if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False):
_LOGGER.warning(
"OTA rollback requires safe_mode, disabling rollback support"
)
advanced[CONF_ENABLE_OTA_ROLLBACK] = False
if errs: if errs:
raise cv.MultipleInvalid(errs) raise cv.MultipleInvalid(errs)
@@ -616,10 +672,13 @@ def require_vfs_dir() -> None:
def _parse_idf_component(value: str) -> ConfigType: def _parse_idf_component(value: str) -> ConfigType:
"""Parse IDF component shorthand syntax like 'owner/component^version'""" """Parse IDF component shorthand syntax like 'owner/component^version'"""
if "^" not in value: # Match operator followed by version-like string (digit or *)
raise cv.Invalid(f"Invalid IDF component shorthand '{value}'") if match := re.search(r"(~=|>=|<=|==|!=|>|<|\^|~)(\d|\*)", value):
name, ref = value.split("^", 1) return {CONF_NAME: value[: match.start()], CONF_REF: value[match.start() :]}
return {CONF_NAME: name, CONF_REF: ref} raise cv.Invalid(
f"Invalid IDF component shorthand '{value}'. "
f"Expected format: 'owner/component<op>version' where <op> is one of: ^, ~, ~=, ==, !=, >=, >, <=, <"
)
def _validate_idf_component(config: ConfigType) -> ConfigType: def _validate_idf_component(config: ConfigType) -> ConfigType:
@@ -661,6 +720,9 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of(
*ESP32_CHIP_REVISIONS
),
# DHCP server is needed for WiFi AP mode. When WiFi component is used, # DHCP server is needed for WiFi AP mode. When WiFi component is used,
# it will handle disabling DHCP server when AP is not configured. # it will handle disabling DHCP server when AP is not configured.
# Default to false (disabled) when WiFi is not used. # Default to false (disabled) when WiFi is not used.
@@ -687,6 +749,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
min=8192, max=32768 min=8192, max=32768
), ),
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
} }
), ),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
@@ -712,12 +775,14 @@ FRAMEWORK_SCHEMA = cv.Schema(
) )
# Remove this class in 2026.7.0
class _FrameworkMigrationWarning: class _FrameworkMigrationWarning:
shown = False shown = False
def _show_framework_migration_message(name: str, variant: str) -> None: def _show_framework_migration_message(name: str, variant: str) -> None:
"""Show a friendly message about framework migration when defaulting to Arduino.""" """Show a message about the framework default change and how to switch back to Arduino."""
# Remove this function in 2026.7.0
if _FrameworkMigrationWarning.shown: if _FrameworkMigrationWarning.shown:
return return
_FrameworkMigrationWarning.shown = True _FrameworkMigrationWarning.shown = True
@@ -727,41 +792,27 @@ def _show_framework_migration_message(name: str, variant: str) -> None:
message = ( message = (
color( color(
AnsiFore.BOLD_CYAN, AnsiFore.BOLD_CYAN,
f"💡 IMPORTANT: {name} doesn't have a framework specified!", f"💡 NOTICE: {name} does not have a framework specified.",
) )
+ "\n\n" + "\n\n"
+ f"Currently, {variant} defaults to the Arduino framework.\n" + f"Starting with ESPHome 2026.1.0, the default framework for {variant} is ESP-IDF.\n"
+ color(AnsiFore.YELLOW, "This will change to ESP-IDF in ESPHome 2026.1.0.\n") + "(We've been warning about this change since ESPHome 2025.8.0)\n"
+ "\n" + "\n"
+ "Note: Newer ESP32 variants (C6, H2, P4, etc.) already use ESP-IDF by default.\n" + "Why we made this change:\n"
+ "\n" + color(AnsiFore.GREEN, " ✨ Up to 40% smaller firmware binaries\n")
+ "Why change? ESP-IDF offers:\n"
+ color(AnsiFore.GREEN, " ✨ Up to 40% smaller binaries\n")
+ color(AnsiFore.GREEN, " 🚀 Better performance and optimization\n")
+ color(AnsiFore.GREEN, " ⚡ 2-3x faster compile times\n") + color(AnsiFore.GREEN, " ⚡ 2-3x faster compile times\n")
+ color(AnsiFore.GREEN, " 📦 Custom-built firmware for your exact needs\n") + color(AnsiFore.GREEN, " 🚀 Better performance and newer features\n")
+ color( + color(AnsiFore.GREEN, " 🔧 More actively maintained by ESPHome\n")
AnsiFore.GREEN,
" 🔧 Active development and testing by ESPHome developers\n",
)
+ "\n" + "\n"
+ "Trade-offs:\n" + "To continue using Arduino, add this to your YAML under 'esp32:':\n"
+ color(AnsiFore.YELLOW, " 🔄 Some components need migration\n") + color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: arduino\n")
+ "\n" + "\n"
+ "What should I do?\n" + "To silence this message with ESP-IDF, explicitly set:\n"
+ color(AnsiFore.CYAN, " Option 1") + color(AnsiFore.WHITE, " framework:\n")
+ ": Migrate to ESP-IDF (recommended)\n" + color(AnsiFore.WHITE, " type: esp-idf\n")
+ " Add this to your YAML under 'esp32:':\n"
+ color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: esp-idf\n")
+ "\n" + "\n"
+ color(AnsiFore.CYAN, " Option 2") + "Migration guide: "
+ ": Keep using Arduino (still supported)\n"
+ " Add this to your YAML under 'esp32:':\n"
+ color(AnsiFore.WHITE, " framework:\n")
+ color(AnsiFore.WHITE, " type: arduino\n")
+ "\n"
+ "Need help? Check out the migration guide:\n"
+ color( + color(
AnsiFore.BLUE, AnsiFore.BLUE,
"https://esphome.io/guides/esp32_arduino_to_idf/", "https://esphome.io/guides/esp32_arduino_to_idf/",
@@ -776,13 +827,13 @@ def _set_default_framework(config):
config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({}) config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
if CONF_TYPE not in config[CONF_FRAMEWORK]: if CONF_TYPE not in config[CONF_FRAMEWORK]:
variant = config[CONF_VARIANT] variant = config[CONF_VARIANT]
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
# Show migration message for variants that previously defaulted to Arduino
# Remove this message in 2026.7.0
if variant in ARDUINO_ALLOWED_VARIANTS: if variant in ARDUINO_ALLOWED_VARIANTS:
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
_show_framework_migration_message( _show_framework_migration_message(
config.get(CONF_NAME, "This device"), variant config.get(CONF_NAME, "This device"), variant
) )
else:
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
return config return config
@@ -967,29 +1018,21 @@ async def to_code(config):
cg.add_platformio_option("framework", "arduino, espidf") cg.add_platformio_option("framework", "arduino, espidf")
cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option(
"board_build.embed_txtfiles",
[
"managed_components/espressif__esp_insights/server_certs/https_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt",
],
)
cg.add_define( cg.add_define(
"USE_ARDUINO_VERSION_CODE", "USE_ARDUINO_VERSION_CODE",
cg.RawExpression( cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
), ),
) )
add_idf_sdkconfig_option(
"CONFIG_ARDUINO_LOOP_STACK_SIZE",
conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE],
)
add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
# Add IDF framework source for Arduino builds to ensure it uses the same version as
# the ESP-IDF framework
if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None:
cg.add_platformio_option(
"platform_packages", [_format_framework_espidf_version(idf_ver, None)]
)
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency # ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
if get_esp32_variant() == VARIANT_ESP32S2: if get_esp32_variant() == VARIANT_ESP32S2:
@@ -1003,6 +1046,16 @@ async def to_code(config):
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
) )
# Set minimum chip revision for ESP32 variant
# Setting this to 3.0 or higher reduces flash size by excluding workaround code,
# and for PSRAM users saves significant IRAM by keeping C library functions in ROM.
if variant == VARIANT_ESP32:
min_rev = conf[CONF_ADVANCED].get(CONF_MINIMUM_CHIP_REVISION)
if min_rev is not None:
for rev, flag in ESP32_CHIP_REVISIONS.items():
add_idf_sdkconfig_option(flag, rev == min_rev)
cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET")
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv") add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv")
@@ -1160,6 +1213,11 @@ async def to_code(config):
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True
) )
# Enable OTA rollback support
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True)
cg.add_define("USE_OTA_ROLLBACK")
cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE]) cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE])
cg.add_define( cg.add_define(
@@ -1189,7 +1247,7 @@ APP_PARTITION_SIZES = {
} }
def get_arduino_partition_csv(flash_size): def get_arduino_partition_csv(flash_size: str):
app_partition_size = APP_PARTITION_SIZES[flash_size] app_partition_size = APP_PARTITION_SIZES[flash_size]
eeprom_partition_size = 0x1000 # 4 KB eeprom_partition_size = 0x1000 # 4 KB
spiffs_partition_size = 0xF000 # 60 KB spiffs_partition_size = 0xF000 # 60 KB
@@ -1209,7 +1267,7 @@ spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:
""" """
def get_idf_partition_csv(flash_size): def get_idf_partition_csv(flash_size: str):
app_partition_size = APP_PARTITION_SIZES[flash_size] app_partition_size = APP_PARTITION_SIZES[flash_size]
return f"""\ return f"""\

View File

@@ -1488,6 +1488,10 @@ BOARDS = {
"name": "Arduino Nano ESP32", "name": "Arduino Nano ESP32",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"arduino_nesso_n1": {
"name": "Arduino Nesso-N1",
"variant": VARIANT_ESP32C6,
},
"atd147_s3": { "atd147_s3": {
"name": "ArtronShop ATD1.47-S3", "name": "ArtronShop ATD1.47-S3",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
@@ -1656,6 +1660,10 @@ BOARDS = {
"name": "Espressif ESP32-C6-DevKitM-1", "name": "Espressif ESP32-C6-DevKitM-1",
"variant": VARIANT_ESP32C6, "variant": VARIANT_ESP32C6,
}, },
"esp32-c61-devkitc1-n8r2": {
"name": "Espressif ESP32-C61-DevKitC-1 N8R2 (8 MB Flash Quad, 2 MB PSRAM Quad)",
"variant": VARIANT_ESP32C61,
},
"esp32-devkitlipo": { "esp32-devkitlipo": {
"name": "OLIMEX ESP32-DevKit-LiPo", "name": "OLIMEX ESP32-DevKit-LiPo",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@@ -1673,11 +1681,15 @@ BOARDS = {
"variant": VARIANT_ESP32H2, "variant": VARIANT_ESP32H2,
}, },
"esp32-p4": { "esp32-p4": {
"name": "Espressif ESP32-P4 generic", "name": "Espressif ESP32-P4 ES (pre rev.300) generic",
"variant": VARIANT_ESP32P4, "variant": VARIANT_ESP32P4,
}, },
"esp32-p4-evboard": { "esp32-p4-evboard": {
"name": "Espressif ESP32-P4 Function EV Board", "name": "Espressif ESP32-P4 Function EV Board (ES pre rev.300)",
"variant": VARIANT_ESP32P4,
},
"esp32-p4_r3": {
"name": "Espressif ESP32-P4 rev.300 generic",
"variant": VARIANT_ESP32P4, "variant": VARIANT_ESP32P4,
}, },
"esp32-pico-devkitm-2": { "esp32-pico-devkitm-2": {
@@ -2093,7 +2105,7 @@ BOARDS = {
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"m5stack-tab5-p4": { "m5stack-tab5-p4": {
"name": "M5STACK Tab5 esp32-p4 Board", "name": "M5STACK Tab5 esp32-p4 Board (ES pre rev.300)",
"variant": VARIANT_ESP32P4, "variant": VARIANT_ESP32P4,
}, },
"m5stack-timer-cam": { "m5stack-timer-cam": {

View File

@@ -4,25 +4,20 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "preferences.h" #include "preferences.h"
#include <freertos/FreeRTOS.h> #include <esp_clk_tree.h>
#include <freertos/task.h> #include <esp_cpu.h>
#include <esp_idf_version.h> #include <esp_idf_version.h>
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#include <esp_timer.h> #include <esp_timer.h>
#include <soc/rtc.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <hal/cpu_hal.h> void setup(); // NOLINT(readability-redundant-declaration)
void loop(); // NOLINT(readability-redundant-declaration)
#ifdef USE_ARDUINO // Weak stub for initArduino - overridden when the Arduino component is present
#include <Esp.h> extern "C" __attribute__((weak)) void initArduino() {}
#else
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include <esp_clk_tree.h>
#endif
void setup();
void loop();
#endif
namespace esphome { namespace esphome {
@@ -41,29 +36,13 @@ void arch_restart() {
void arch_init() { void arch_init() {
// Enable the task watchdog only on the loop task (from which we're currently running) // Enable the task watchdog only on the loop task (from which we're currently running)
#if defined(USE_ESP_IDF)
esp_task_wdt_add(nullptr); esp_task_wdt_add(nullptr);
// Idle task watchdog is disabled on ESP-IDF
#elif defined(USE_ARDUINO)
enableLoopWDT();
// Disable idle task watchdog on the core we're using (Arduino pins the task to a core)
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0
disableCore0WDT();
#endif
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1
disableCore1WDT();
#endif
#endif
// If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current // Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled,
// partition will get rolled back unless it is marked as valid. // in which case safe_mode will mark it valid after confirming successful boot.
esp_ota_img_states_t state; #ifndef USE_OTA_ROLLBACK
const esp_partition_t *running = esp_ota_get_running_partition(); esp_ota_mark_app_valid_cancel_rollback();
if (esp_ota_get_state_partition(running, &state) == ESP_OK) { #endif
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
esp_ota_mark_app_valid_cancel_rollback();
}
}
} }
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
@@ -71,21 +50,10 @@ uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
uint32_t arch_get_cpu_freq_hz() { uint32_t arch_get_cpu_freq_hz() {
uint32_t freq = 0; uint32_t freq = 0;
#ifdef USE_ESP_IDF
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq); esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
#else
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_get_config(&config);
freq = config.freq_mhz * 1000000U;
#endif
#elif defined(USE_ARDUINO)
freq = ESP.getCpuFreqMHz() * 1000000;
#endif
return freq; return freq;
} }
#ifdef USE_ESP_IDF
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void loop_task(void *pv_params) { void loop_task(void *pv_params) {
@@ -96,6 +64,7 @@ void loop_task(void *pv_params) {
} }
extern "C" void app_main() { extern "C" void app_main() {
initArduino();
esp32::setup_preferences(); esp32::setup_preferences();
#if CONFIG_FREERTOS_UNICORE #if CONFIG_FREERTOS_UNICORE
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle); xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle);
@@ -103,11 +72,6 @@ extern "C" void app_main() {
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1); xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1);
#endif #endif
} }
#endif // USE_ESP_IDF
#ifdef USE_ARDUINO
extern "C" void init() { esp32::setup_preferences(); }
#endif // USE_ARDUINO
} // namespace esphome } // namespace esphome

View File

@@ -97,10 +97,8 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
gpio_isr_handler_add(this->get_pin_num(), func, arg); gpio_isr_handler_add(this->get_pin_num(), func, arg);
} }
std::string ESP32InternalGPIOPin::dump_summary() const { size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {
char buffer[32]; return snprintf(buffer, len, "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
return buffer;
} }
void ESP32InternalGPIOPin::setup() { void ESP32InternalGPIOPin::setup() {

View File

@@ -24,7 +24,7 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
void pin_mode(gpio::Flags flags) override; void pin_mode(gpio::Flags flags) override;
bool digital_read() override; bool digital_read() override;
void digital_write(bool value) override; void digital_write(bool value) override;
std::string dump_summary() const override; size_t dump_summary(char *buffer, size_t len) const override;
void detach_interrupt() const override; void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override; ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return this->pin_; } uint8_t get_pin() const override { return this->pin_; }

View File

@@ -5,6 +5,7 @@ import json # noqa: E402
import os # noqa: E402 import os # noqa: E402
import pathlib # noqa: E402 import pathlib # noqa: E402
import shutil # noqa: E402 import shutil # noqa: E402
from glob import glob # noqa: E402
def merge_factory_bin(source, target, env): def merge_factory_bin(source, target, env):
@@ -126,3 +127,14 @@ def esp32_copy_ota_bin(source, target, env):
# Run merge first, then ota copy second # Run merge first, then ota copy second
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821
# Find server certificates in managed components and generate .S files.
# Workaround for PlatformIO not processing target_add_binary_data() from managed component CMakeLists.
project_dir = env.subst("$PROJECT_DIR")
managed_components = os.path.join(project_dir, "managed_components")
if os.path.isdir(managed_components):
for cert_file in glob(os.path.join(managed_components, "**/server_certs/*.crt"), recursive=True):
try:
env.FileToAsm(cert_file, FILE_TYPE="TEXT")
except Exception as e:
print(f"Error processing {os.path.basename(cert_file)}: {e}")

View File

@@ -4,26 +4,28 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include <nvs_flash.h> #include <nvs_flash.h>
#include <cstring>
#include <cinttypes> #include <cinttypes>
#include <vector> #include <cstring>
#include <string>
#include <memory> #include <memory>
#include <vector>
namespace esphome { namespace esphome {
namespace esp32 { namespace esp32 {
static const char *const TAG = "esp32.preferences"; static const char *const TAG = "esp32.preferences";
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
static constexpr size_t KEY_BUFFER_SIZE = 12;
struct NVSData { struct NVSData {
std::string key; uint32_t key;
std::unique_ptr<uint8_t[]> data; std::unique_ptr<uint8_t[]> data;
size_t len; size_t len;
void set_data(const uint8_t *src, size_t size) { void set_data(const uint8_t *src, size_t size) {
data = std::make_unique<uint8_t[]>(size); this->data = std::make_unique<uint8_t[]>(size);
memcpy(data.get(), src, size); memcpy(this->data.get(), src, size);
len = size; this->len = size;
} }
}; };
@@ -31,27 +33,27 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
class ESP32PreferenceBackend : public ESPPreferenceBackend { class ESP32PreferenceBackend : public ESPPreferenceBackend {
public: public:
std::string key; uint32_t key;
uint32_t nvs_handle; uint32_t nvs_handle;
bool save(const uint8_t *data, size_t len) override { bool save(const uint8_t *data, size_t len) override {
// try find in pending saves and update that // try find in pending saves and update that
for (auto &obj : s_pending_save) { for (auto &obj : s_pending_save) {
if (obj.key == key) { if (obj.key == this->key) {
obj.set_data(data, len); obj.set_data(data, len);
return true; return true;
} }
} }
NVSData save{}; NVSData save{};
save.key = key; save.key = this->key;
save.set_data(data, len); save.set_data(data, len);
s_pending_save.emplace_back(std::move(save)); s_pending_save.emplace_back(std::move(save));
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
return true; return true;
} }
bool load(uint8_t *data, size_t len) override { bool load(uint8_t *data, size_t len) override {
// try find in pending saves and load from that // try find in pending saves and load from that
for (auto &obj : s_pending_save) { for (auto &obj : s_pending_save) {
if (obj.key == key) { if (obj.key == this->key) {
if (obj.len != len) { if (obj.len != len) {
// size mismatch // size mismatch
return false; return false;
@@ -61,22 +63,24 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
} }
} }
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
size_t actual_len; size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
if (err != 0) { if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err)); ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
return false; return false;
} }
if (actual_len != len) { if (actual_len != len) {
ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
return false; return false;
} }
err = nvs_get_blob(nvs_handle, key.c_str(), data, &len); err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
if (err != 0) { if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err)); ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
return false; return false;
} else { } else {
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len); ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
} }
return true; return true;
} }
@@ -103,14 +107,12 @@ class ESP32Preferences : public ESPPreferences {
} }
} }
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
return make_preference(length, type); return this->make_preference(length, type);
} }
ESPPreferenceObject make_preference(size_t length, uint32_t type) override { ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->nvs_handle = nvs_handle; pref->nvs_handle = this->nvs_handle;
pref->key = type;
uint32_t keyval = type;
pref->key = str_sprintf("%" PRIu32, keyval);
return ESPPreferenceObject(pref); return ESPPreferenceObject(pref);
} }
@@ -123,17 +125,19 @@ class ESP32Preferences : public ESPPreferences {
// goal try write all pending saves even if one fails // goal try write all pending saves even if one fails
int cached = 0, written = 0, failed = 0; int cached = 0, written = 0, failed = 0;
esp_err_t last_err = ESP_OK; esp_err_t last_err = ESP_OK;
std::string last_key{}; uint32_t last_key = 0;
// go through vector from back to front (makes erase easier/more efficient) // go through vector from back to front (makes erase easier/more efficient)
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
const auto &save = s_pending_save[i]; const auto &save = s_pending_save[i];
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str()); char key_str[KEY_BUFFER_SIZE];
if (is_changed(nvs_handle, save)) { snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len); ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); if (this->is_changed_(this->nvs_handle, save, key_str)) {
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
if (err != 0) { if (err != 0) {
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err)); ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err));
failed++; failed++;
last_err = err; last_err = err;
last_key = save.key; last_key = save.key;
@@ -141,7 +145,7 @@ class ESP32Preferences : public ESPPreferences {
} }
written++; written++;
} else { } else {
ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len); ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len);
cached++; cached++;
} }
s_pending_save.erase(s_pending_save.begin() + i); s_pending_save.erase(s_pending_save.begin() + i);
@@ -149,12 +153,12 @@ class ESP32Preferences : public ESPPreferences {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed); failed);
if (failed > 0) { if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%s", failed, esp_err_to_name(last_err), ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
last_key.c_str()); last_key);
} }
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
esp_err_t err = nvs_commit(nvs_handle); esp_err_t err = nvs_commit(this->nvs_handle);
if (err != 0) { if (err != 0) {
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
return false; return false;
@@ -162,11 +166,13 @@ class ESP32Preferences : public ESPPreferences {
return failed == 0; return failed == 0;
} }
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
protected:
bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) {
size_t actual_len; size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len); esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
if (err != 0) { if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err)); ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
return true; return true;
} }
// Check size first before allocating memory // Check size first before allocating memory
@@ -174,9 +180,9 @@ class ESP32Preferences : public ESPPreferences {
return true; return true;
} }
auto stored_data = std::make_unique<uint8_t[]>(actual_len); auto stored_data = std::make_unique<uint8_t[]>(actual_len);
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len); err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
if (err != 0) { if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
return true; return true;
} }
return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0; return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;

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