1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-10 09:42:01 +00:00

Compare commits

..

192 Commits

Author SHA1 Message Date
Cody Cutrer
b97a728cf1 [ld2450] add on_data callback (#13601)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-09 22:40:44 -05:00
Jonathan Swoboda
dcbb020479 [uart] Fix available() return type to size_t across components (#13898)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 20:02:41 -05:00
J. Nick Koston
87ac263264 [dsmr] Batch UART reads to reduce per-loop overhead (#13826) 2026-02-10 00:32:52 +00:00
Sean Kelly
097901e9c8 [aqi] Fix AQI calculation for specific pm2.5 or pm10 readings (#13770) 2026-02-09 19:30:37 -05:00
J. Nick Koston
01a90074ba [ld2420] Batch UART reads to reduce loop overhead (#13821)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-02-10 00:25:34 +00:00
J. Nick Koston
57b85a8400 [dlms_meter] Batch UART reads to reduce per-loop overhead (#13828)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-02-10 00:24:20 +00:00
J. Nick Koston
2edfcf278f [hlk_fm22x] Replace per-cycle vector allocation with member buffer (#13859) 2026-02-09 18:21:10 -06:00
J. Nick Koston
bcd4a9fc39 [pylontech] Batch UART reads to reduce loop overhead (#13824) 2026-02-09 18:20:53 -06:00
J. Nick Koston
78df8be31f [logger] Resolve thread name once and pass through logging chain (#13836) 2026-02-09 18:16:27 -06:00
J. Nick Koston
dacc557a16 [uart] Convert parity_to_str to PROGMEM_STRING_TABLE (#13805) 2026-02-09 18:15:48 -06:00
J. Nick Koston
3767c5ec91 [scheduler] Make core timer ID collisions impossible with type-safe internal IDs (#13882)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-02-09 16:48:08 -06:00
George Joseph
7c1327f96a [mipi_dsi] Add WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD 3.4C and 4C (#13840) 2026-02-10 09:44:47 +11:00
Jonathan Swoboda
475db750e0 [uart] Change available() return type from int to size_t (#13893)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:41:16 -05:00
dependabot[bot]
8f74b027b4 Bump setuptools from 80.10.2 to 82.0.0 (#13897) 2026-02-09 16:40:32 -06:00
tomaszduda23
b2b9e0cb0a [nrf52,zigee] print reporting status (#13890)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-02-09 16:00:08 -05:00
tronikos
dbf202bf0d Add get_away and get_on in WaterHeaterCall and deprecate get_state (#13891) 2026-02-09 20:57:36 +00:00
J. Nick Koston
b6fdd29953 [voice_assistant] Replace timer unordered_map with vector to eliminate per-tick heap allocation (#13857) 2026-02-09 14:42:40 -06:00
Clyde Stubbs
00256e3ca0 [mipi_rgb] Allow use on P4 (#13740) 2026-02-10 06:35:41 +11:00
J. Nick Koston
e0712cc53b [scheduler] Make core timer ID collisions impossible with type-safe internal IDs (#13882)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-02-09 13:16:22 -06:00
J. Nick Koston
6c6da8a3cd [api] Skip class generation for empty SOURCE_CLIENT protobuf messages (#13880) 2026-02-09 18:45:24 +00:00
J. Nick Koston
e4ea016d1e [ci] Block new std::to_string() usage, suggest snprintf alternatives (#13369) 2026-02-09 12:26:19 -06:00
J. Nick Koston
41a9588d81 [i2c] Replace switch with if-else to avoid CSWTCH table in RAM (#13815) 2026-02-09 12:26:06 -06:00
J. Nick Koston
cd55eb927d [modbus] Batch UART reads to reduce loop overhead (#13822) 2026-02-09 12:21:15 -06:00
J. Nick Koston
4a9ff48f02 [nextion] Batch UART reads to reduce loop overhead (#13823)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-09 12:20:50 -06:00
J. Nick Koston
8fffe7453d [seeed_mr24hpc1/mr60fda2/mr60bha2] Batch UART reads to reduce per-loop overhead (#13825) 2026-02-09 12:18:12 -06:00
J. Nick Koston
a5ee451043 [tuya] Batch UART reads to reduce per-loop overhead (#13827) 2026-02-09 12:17:58 -06:00
J. Nick Koston
e176cf50ab [dfplayer] Batch UART reads to reduce per-loop overhead (#13832) 2026-02-09 12:15:28 -06:00
J. Nick Koston
e7a900fbaa [rf_bridge] Batch UART reads to reduce per-loop overhead (#13831) 2026-02-09 12:15:15 -06:00
J. Nick Koston
623f33c9f9 [rd03d] Batch UART reads to reduce per-loop overhead (#13830) 2026-02-09 12:15:04 -06:00
J. Nick Koston
8b24112be5 [pipsolar] Batch UART reads to reduce per-loop overhead (#13829) 2026-02-09 12:14:48 -06:00
J. Nick Koston
d33f23dc43 [ld2410] Batch UART reads to reduce loop overhead (#13820) 2026-02-09 12:07:55 -06:00
J. Nick Koston
c43d3889b0 [modbus] Use stack buffer instead of heap vector in send() (#13853) 2026-02-09 12:07:42 -06:00
J. Nick Koston
50fe8e51f9 [ld2412] Batch UART reads to reduce loop overhead (#13819) 2026-02-09 12:07:28 -06:00
J. Nick Koston
c7883cb5ae [ld2450] Batch UART reads to reduce loop overhead (#13818) 2026-02-09 12:06:38 -06:00
J. Nick Koston
3b0df145b7 [cse7766] Batch UART reads to reduce loop overhead (#13817) 2026-02-09 12:05:59 -06:00
J. Nick Koston
2383b6b8b4 [core] Deprecate set_retry, cancel_retry, and RetryResult (#13845) 2026-02-09 12:05:32 -06:00
J. Nick Koston
c658d7b57f [api] Merge auth check into base read_message, eliminate APIServerConnection (#13873) 2026-02-09 12:02:02 -06:00
Jonathan Swoboda
04a6238c7b [esp32] Set UV_CACHE_DIR inside data dir so Clean All clears it (#13888)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:49:58 +00:00
J. Nick Koston
919afa1553 [web_server_base] Fix RP2040 compilation when Crypto-no-arduino is present (#13887) 2026-02-09 12:47:59 -05:00
Kevin Ahrendt
c28c97fbaf [mixer] Refactor for stability and to support Sendspin (#12253)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-02-09 10:19:00 -05:00
J. Nick Koston
3cde3daceb [api] Collapse APIServerConnection intermediary layer (#13872) 2026-02-09 08:45:33 -06:00
J. Nick Koston
be4e573cc4 [esp32_hosted] Replace set_retry with set_interval to avoid heap allocation (#13844) 2026-02-09 08:45:18 -06:00
J. Nick Koston
66af998098 [dashboard] Handle malformed Basic Auth headers gracefully (#13866) 2026-02-09 08:45:03 -06:00
J. Nick Koston
938a11595d [speaker] Replace set_retry with set_interval to avoid heap allocation (#13843) 2026-02-09 08:44:50 -06:00
J. Nick Koston
c812ac8b29 [ms8607] Replace set_retry with set_timeout chain to avoid heap allocation (#13842) 2026-02-09 08:44:35 -06:00
J. Nick Koston
248fc06dac [scheduler] Eliminate heap allocation in full_cleanup_removed_items_ (#13837) 2026-02-09 08:44:20 -06:00
J. Nick Koston
8b8acb3b27 [dashboard] Use constant-time comparison for username check (#13865) 2026-02-09 08:31:06 -06:00
J. Nick Koston
1c60efa4b6 [ota] Use secrets module for OTA authentication cnonce (#13863) 2026-02-09 08:30:49 -06:00
J. Nick Koston
4ef238eb7b [analyze-memory] Attribute third-party library symbols via nm scanning (#13878) 2026-02-09 08:26:03 -06:00
J. Nick Koston
22c77866d8 [e131] Remove unnecessary heap allocation from packet receive loop (#13852) 2026-02-09 06:42:26 -06:00
J. Nick Koston
790ac620ab [web_server_idf] Use C++17 nested namespace style (#13856) 2026-02-09 06:42:12 -06:00
tronikos
fb93283720 [water_heater] Add state masking to distinguish explicit commands from no-change (#13879) 2026-02-09 03:52:49 -06:00
J. Nick Koston
bed01da345 [api] Guard varint parsing against overlong encodings (#13870) 2026-02-09 03:45:40 -06:00
J. Nick Koston
422f413680 [lps22] Replace set_retry with set_interval to avoid heap allocation (#13841) 2026-02-09 03:26:44 -06:00
J. Nick Koston
c3c0c40524 [mqtt] Return friendly_name_() by const reference to avoid string copies (#13810) 2026-02-09 03:26:29 -06:00
J. Nick Koston
46f8302d8f [mqtt] Use stack buffer for discovery topic to avoid heap allocation (#13812) 2026-02-09 03:26:15 -06:00
J. Nick Koston
e24528c842 [analyze-memory] Attribute CSWTCH symbols from SDK archives (#13850) 2026-02-09 03:25:59 -06:00
J. Nick Koston
5370687001 [wizard] Use secrets module for fallback AP password generation (#13864) 2026-02-09 03:25:41 -06:00
J. Nick Koston
6ee185c58a [dashboard] Use resolve/relative_to for download path validation (#13867) 2026-02-09 03:25:23 -06:00
J. Nick Koston
eb6a6f8d0d [web_server_idf] Remove unused host() method (#13869) 2026-02-09 03:25:05 -06:00
J. Nick Koston
140ec0639c [api] Elide empty message construction in protobuf dispatch (#13871) 2026-02-09 03:24:45 -06:00
Clyde Stubbs
756f1c6b7e [lvgl] Fix crash with unconfigured top_layer (#13846) 2026-02-08 21:53:43 -05:00
tomaszduda23
28b9487b25 [nrf52,logger] fix printk (#13874) 2026-02-08 17:52:05 +00:00
J. Nick Koston
41fedaedb3 [udp] Eliminate per-loop heap allocation using std::span (#13838)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-02-08 08:26:47 -06:00
schrob
7b40e8afcb [epaper_spi] Declare leaf classes final (#13776) 2026-02-07 19:21:37 -06:00
J. Nick Koston
a43e3e5948 [dashboard] Close WebSocket after process exit to prevent zombie connections (#13834) 2026-02-07 15:19:20 -06:00
schrob
9de91539e6 [epaper_spi] Add Waveshare 1.54-G (#13758) 2026-02-08 06:24:57 +11:00
tronikos
eb7aa3420f Add target_temperature to the template water heater (#13661)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-02-06 21:23:42 +01:00
J. Nick Koston
86f91eed2f [mqtt] Move switch string tables to PROGMEM_STRING_TABLE (#13802)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-02-06 19:30:05 +01:00
J. Nick Koston
41cecbfb0f [template] Convert alarm sensor type to PROGMEM_STRING_TABLE and narrow enum to uint8_t (#13804) 2026-02-06 18:22:26 +00:00
Jonathan Swoboda
9315da79bc [core] Add missing requests dependency to requirements.txt (#13803)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:03:16 -05:00
PolarGoose
155447f541 [dsmr] Fix issue with parsing lines like 1-0:0.2.0((ER11)) (#13780) 2026-02-06 12:53:59 -05:00
J. Nick Koston
238e40966f [light] Move CSWTCH lookup table to PROGMEM in get_suitable_color_modes_mask_ (#13801) 2026-02-06 17:33:26 +00:00
J. Nick Koston
f9192b5f75 [wifi] Avoid jump tables in LOG_STR switch statements to save ESP8266 RAM (#13799) 2026-02-06 18:20:46 +01:00
J. Nick Koston
2917057da8 [analyze-memory] Trace CSWTCH switch table symbols to source components (#13798)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-02-06 18:08:30 +01:00
J. Nick Koston
c7c9ffe7e1 [light] Convert color_mode_to_human to PROGMEM_STRING_TABLE using to_bit() (#13797) 2026-02-06 17:38:03 +01:00
J. Nick Koston
368ef5687b [update] Move update_state_to_string to update component and convert to PROGMEM_STRING_TABLE (#13796)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-06 17:37:41 +01:00
J. Nick Koston
b7dc975331 [core] Convert entity string lookups to PROGMEM_STRING_TABLE (#13794) 2026-02-06 17:37:19 +01:00
J. Nick Koston
44f308502e [gpio] Convert interrupt_type_to_string to PROGMEM_STRING_TABLE (#13795)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-06 17:37:02 +01:00
J. Nick Koston
ec477801ca [wifi] Defer ESP8266 WiFi listener callbacks from system context to main loop (#13789) 2026-02-06 16:23:19 +00:00
J. Nick Koston
c3622ef7fb [http_request] Fix chunked transfer encoding on Arduino platforms (#13790) 2026-02-06 15:52:41 +01:00
J. Nick Koston
e4ad2082bc [core] Add PROGMEM_STRING_TABLE macro for flash-optimized string lookups (#13659)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-06 15:26:16 +01:00
Andrew Rankin
7afd0eb1aa [esp32_ble] include sdkconfig.h before ESP-Hosted preprocessor guards (#13787)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:36:55 +00:00
Clyde Stubbs
112a2c5d92 [const] Move some constants to common (#13788) 2026-02-06 20:11:08 +11:00
Jonathan Swoboda
fef5d3f88f [rdm6300] Add ID-20LA compatibility by skipping CR/LF bytes (#13779)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 04:10:22 -05:00
Jonathan Swoboda
8e461db301 [ota] Fix CLI upload option shown when only http_request platform configured (#13784)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 04:09:48 -05:00
dependabot[bot]
6decdfad26 Bump github/codeql-action from 4.32.1 to 4.32.2 (#13781)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 10:05:10 +01:00
Jonathan Swoboda
c7729cb019 [esp32] Use underscores in arduino_libs_stub folder name (#13785)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:51:13 +01:00
Marek Beran
ed4f00d4a3 [vbus] Add DeltaSol BS/2 support with sensors and binary sensors (#13762) 2026-02-05 23:11:14 -08:00
J. Nick Koston
55ef8393af [api] Remove is_single parameter and fix batch buffer preparation (#13773)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 15:19:03 +01:00
Jonathan Swoboda
081f953dc3 [core] Add capacity check to register_component_ (#13778)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 14:00:16 +00:00
J. Nick Koston
f4e410f47f [ci] Block new scanf() usage to prevent ~9.8KB flash bloat (#13657) 2026-02-06 02:56:43 +13:00
schrob
bbdb202e2c [epaper_spi] Refactor initialise for future use (#13774) 2026-02-06 02:26:47 +13:00
Jonathan Swoboda
9ea8461440 [esp32] Remove specific claims from framework migration message (#13777)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:41:17 +00:00
Jonathan Swoboda
ed8c0dc99d [esp32] Skip downloading precompiled Arduino libs (#13775)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 05:55:08 -05:00
J. Nick Koston
be44d4801f [esp32] Reduce Arduino build size by 44% and build time by 36% (#13623) 2026-02-05 10:52:43 +01:00
Jas Strong
7bd8b08e16 [rd03d] Revert incorrect field order swap (#13769)
Co-authored-by: jas <jas@asspa.in>
2026-02-05 03:06:52 -05:00
J. Nick Koston
c27870b15d [web_server] Add some more missing ESPHOME_F macros (#13748) 2026-02-05 06:36:40 +01:00
J. Nick Koston
25c0073b2d [web_server] Fix ESP8266 watchdog panic by deferring actions to main loop (#13765) 2026-02-05 06:20:04 +01:00
J. Nick Koston
a556824875 [logger] Refactor to reduce code duplication and flash size (#13750) 2026-02-05 06:19:13 +01:00
J. Nick Koston
89fc5ebc97 Fix bare hostname ping fallback in dashboard (#13760) 2026-02-05 06:18:03 +01:00
schrob
67dfa5e2bc [epaper_spi] Validate BUSY pin as input instead of output (#13764) 2026-02-04 23:39:03 +00:00
tomaszduda23
13ddf267bb [nrf52,zigbee] update warnings (#13761) 2026-02-04 15:18:24 -05:00
Jonathan Swoboda
43d9d6fe64 [esp32] Restore develop branch for dev platform version, bump platformio (#13759)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-04 15:12:42 -05:00
Copilot
4a579700a0 [cover] Add operation-based triggers and fix repeated trigger firing (#13471) 2026-02-05 06:52:14 +11:00
Jesse Hills
c1b412d5f3 Merge branch 'release' into dev 2026-02-04 17:56:36 +01:00
J. Nick Koston
becb6559f1 [components] Remove redundant setup priority overrides that duplicate default (#13745) 2026-02-04 10:48:41 -06:00
functionpointer
36f2654fa6 [pylontech] Refactor parser to support new firmware version and SysError (#12300) 2026-02-04 17:06:59 +01:00
Jonathan Swoboda
ba18a8b3e3 [adc] Fix ESP32-C2 ADC calibration to use line fitting (#13756)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:44:17 +00:00
Jesse Hills
ab8ac72c4f Merge pull request #13757 from esphome/bump-2026.1.4
2026.1.4
2026-02-05 00:01:14 +13:00
Jesse Hills
1b3c9aa98e Bump version to 2026.1.4 2026-02-04 11:01:32 +01:00
Samuel Sieb
bafbd4235a [ultrasonic] adjust timeouts and bring the parameter back (#13738)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-02-04 11:01:31 +01:00
J. Nick Koston
900aab45f1 [wifi] Fix wifi.connected condition returning false in connect state listener automations (#13733) 2026-02-04 11:01:29 +01:00
J. Nick Koston
bc41d25657 [cse7766] Fix power reading stuck when load switches off (#13734) 2026-02-04 10:56:42 +01:00
J. Nick Koston
094d64f872 [http_request] Fix requests taking full timeout when response is already complete (#13649) 2026-02-04 10:56:42 +01:00
J. Nick Koston
b085585461 [core] Add missing uint32_t ID overloads for defer() and cancel_defer() (#13720) 2026-02-04 10:56:42 +01:00
rwrozelle
49ef4e00df [mqtt] resolve warnings related to use of ip.str() (#13719) 2026-02-04 10:56:42 +01:00
Jonathan Swoboda
8314ad9ca0 [max7219] Allocate buffer in constructor (#13660)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 10:56:42 +01:00
J0k3r2k1
5544f0d346 [mipi_spi] Fix log_pin() FlashStringHelper compatibility (#13624)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-02-04 10:56:34 +01:00
Samuel Sieb
5dc8bfe95e [ultrasonic] adjust timeouts and bring the parameter back (#13738)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-02-04 04:29:27 -05:00
dependabot[bot]
4d05cd3059 Bump ruff from 0.14.14 to 0.15.0 (#13752)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-02-04 09:24:05 +00:00
J. Nick Koston
2541ec1565 [wifi] Fix wifi.connected condition returning false in connect state listener automations (#13733) 2026-02-04 21:42:13 +13:00
Jonathan Swoboda
95f39149d7 [rtttl] Fix dotted note parsing order to match RTTTL spec (#13722)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:28:59 -05:00
Jonathan Swoboda
e6bae1a97e [adc] Add ESP32-C2 support for curve fitting calibration (#13749)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:16:13 -05:00
J. Nick Koston
f11b8615da [cse7766] Fix power reading stuck when load switches off (#13734) 2026-02-04 05:03:02 +13:00
J. Nick Koston
5d4bde98dc [mqtt] Refactor state publishing with dedicated enum-to-string helpers (#13544)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-02-04 04:56:48 +13:00
J. Nick Koston
b8b072cf86 [web_server_idf] Add const char* overloads for getParam/hasParam to avoid temporary string allocations (#13746) 2026-02-04 04:43:27 +13:00
J. Nick Koston
18f7e0e6b3 [pulse_counter][hlw8012] Fix ESP-IDF build by re-enabling legacy driver component (#13747) 2026-02-03 15:42:45 +00:00
J. Nick Koston
8d0ce49eb4 [api] Eliminate intermediate buffers in protobuf dump helpers (#13742)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-03 16:34:15 +01:00
Tomer Shalev
21bd0ff6aa [mqtt] Stop sending deprecated color_mode and brightness in light discovery (fixes #13666) (#13667) 2026-02-03 14:37:27 +01:00
J. Nick Koston
d0017ded5b [template] Split TemplateSelect into TemplateSelectWithSetAction to save RAM (#13685)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-02-03 11:48:31 +00:00
J. Nick Koston
f4d7d06c41 [dlms_meter] Rename test UART package key to match directory name (#13743) 2026-02-03 11:23:05 +00:00
J. Nick Koston
c027d9116f [template] Add additional tests for template select (#13741) 2026-02-03 11:13:03 +00:00
Clyde Stubbs
b3e09e5c68 [key_collector] Add text sensor and allow multiple callbacks (#13617) 2026-02-03 21:14:09 +11:00
J. Nick Koston
d4110bf650 [lock] Store state strings in flash and avoid heap allocation in set_state (#13729) 2026-02-03 05:29:24 +01:00
Andrew Gillis
ff6f7d3248 [mipi_dsi] Add WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B (#13608) 2026-02-03 14:59:51 +11:00
Roger Fachini
a430b3a426 [speaker.media_player]: Add verbose error message for puremagic parsing (#13725)
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-02-03 03:46:46 +00:00
J. Nick Koston
fbeb0e8e54 [opentherm] Fix ESP-IDF build by re-enabling legacy driver component (#13732) 2026-02-03 03:40:44 +00:00
J. Nick Koston
9d63642bdb [media_player] Store command strings in flash and avoid heap allocation in set_command (#13731) 2026-02-03 04:29:43 +01:00
J. Nick Koston
8cb701e412 [water_heater] Store mode strings in flash and avoid heap allocation in set_mode (#13728) 2026-02-03 04:29:31 +01:00
J. Nick Koston
d41c84d624 [wifi] Conditionally compile on_connect/on_disconnect triggers (#13684) 2026-02-03 04:29:18 +01:00
J. Nick Koston
9f1a427ce2 [preferences] Use static storage for singletons and flash buffer (#13727) 2026-02-03 04:03:52 +01:00
J. Nick Koston
ae71f07abb [http_request] Fix requests taking full timeout when response is already complete (#13649) 2026-02-03 03:19:38 +01:00
J. Nick Koston
ccf5c1f7e9 [esp32] Exclude additional unused IDF components (driver, dac, mcpwm, twai, openthread, ulp) (#13664) 2026-02-03 03:12:12 +01:00
dependabot[bot]
efecea9450 Bump github/codeql-action from 4.32.0 to 4.32.1 (#13726)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 02:27:34 +01:00
J. Nick Koston
26e4cda610 [logger] Use vsnprintf_P directly for ESP8266 flash format strings (#13716) 2026-02-03 02:25:54 +01:00
Jan Kundrát
a6543d32bd [sx126x] fix maximal payload_length (#13723) 2026-02-02 20:15:18 -05:00
Jonathan Swoboda
da947d060f [wizard] Use API encryption key instead of deprecated password (#13634)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 19:20:24 -05:00
J. Nick Koston
1119003eb5 [core] Add missing uint32_t ID overloads for defer() and cancel_defer() (#13720) 2026-02-02 22:22:11 +01:00
J. Nick Koston
c089d9aeac [esp32_hosted] Replace sscanf with strtol for version parsing (#13658) 2026-02-02 22:21:52 +01:00
J. Nick Koston
4f0894e970 [analyze-memory] Add top 30 largest symbols to report (#13673) 2026-02-02 22:05:39 +01:00
J. Nick Koston
848c237159 [time] Use lazy callback for time sync to save 8 bytes (#13652) 2026-02-02 22:05:27 +01:00
J. Nick Koston
6892805094 [api] Align water_heater_command with standard entity command pattern (#13655) 2026-02-02 22:00:46 +01:00
Roger Fachini
aa8ccfc32b [ethernet] Add on_connect and on_disconnect triggers (#13677)
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-02-02 17:00:11 +01:00
rwrozelle
18991686ab [mqtt] resolve warnings related to use of ip.str() (#13719) 2026-02-02 16:48:08 +01:00
J. Nick Koston
62f34bea83 [template.output] Avoid heap allocation for triggers (#13709) 2026-02-02 07:36:27 +01:00
J. Nick Koston
6114005952 [template.water_heater] Avoid heap allocation for trigger (#13712) 2026-02-02 07:36:08 +01:00
J. Nick Koston
c0e5ae4298 [template.text] Avoid heap allocation for trigger (#13711) 2026-02-02 07:35:21 +01:00
J. Nick Koston
420de987bc [micro_wake_word] Avoid heap allocation for trigger (#13714) 2026-02-02 07:35:03 +01:00
J. Nick Koston
61e33217cd [cc1101] Avoid heap allocation for trigger (#13715) 2026-02-02 07:34:50 +01:00
J. Nick Koston
b5b9a89561 [light] Avoid heap allocation for AutomationLightEffect trigger (#13713) 2026-02-02 07:34:34 +01:00
J. Nick Koston
bc9fc66225 [template.datetime] Avoid heap allocation for triggers (#13710) 2026-02-02 04:30:46 +00:00
J. Nick Koston
6727fe9040 [remote_transmitter] Avoid heap allocation for triggers (#13708) 2026-02-02 04:18:17 +00:00
J. Nick Koston
56110d4495 [time_based] Avoid heap allocation for cover triggers (#13703) 2026-02-02 05:15:50 +01:00
J. Nick Koston
1362ff6cba [speaker.media_player] Avoid heap allocation for triggers (#13707) 2026-02-02 05:15:33 +01:00
J. Nick Koston
dbd7401721 [feedback] Avoid heap allocation for cover triggers (#13693) 2026-02-02 05:15:13 +01:00
J. Nick Koston
f0801ecac0 [template.lock] Avoid heap allocation for triggers (#13704) 2026-02-02 05:14:11 +01:00
J. Nick Koston
379652f631 [thermostat] Remove dead null checks for triggers (#13706) 2026-02-02 04:10:08 +00:00
J. Nick Koston
18c152723c [sprinkler] Avoid heap allocation for triggers (#13705) 2026-02-02 04:53:46 +01:00
J. Nick Koston
09b76d5e4a [voice_assistant] Avoid heap allocation for triggers (#13689) 2026-02-02 04:50:16 +01:00
J. Nick Koston
8791c24072 [api] Avoid heap allocation for client connected/disconnected triggers (#13688) 2026-02-02 04:50:01 +01:00
J. Nick Koston
652c02b9ab [bang_bang] Avoid heap allocation for climate triggers (#13701) 2026-02-02 04:49:46 +01:00
J. Nick Koston
4ab552d750 [http_request] Avoid heap allocation for triggers (#13690) 2026-02-02 04:47:49 +01:00
J. Nick Koston
e420964b93 [template.switch] Avoid heap allocation for triggers (#13691) 2026-02-02 04:47:34 +01:00
J. Nick Koston
7d717a78dc [template] Avoid heap allocation for number set trigger (#13694) 2026-02-02 04:47:21 +01:00
J. Nick Koston
2f0abd5c3f [template] Avoid heap allocation for cover triggers (#13696) 2026-02-02 04:46:55 +01:00
J. Nick Koston
d49d8095df [template] Avoid heap allocation for valve triggers (#13697) 2026-02-02 04:46:41 +01:00
J. Nick Koston
8a8c1290db [endstop] Avoid heap allocation for cover triggers (#13702) 2026-02-02 04:45:01 +01:00
J. Nick Koston
01ffeba2c2 [api] Avoid heap allocation for homeassistant action triggers (#13695) 2026-02-02 04:44:08 +01:00
J. Nick Koston
78ed898f0b [current_based] Avoid heap allocation for cover triggers (#13700) 2026-02-02 04:43:52 +01:00
J. Nick Koston
75ee9a718a [sx126x] Avoid heap allocation for packet trigger (#13699) 2026-02-02 04:43:30 +01:00
J. Nick Koston
bfeb447178 [sx127x] Avoid heap allocation for packet trigger (#13698) 2026-02-02 04:43:16 +01:00
J. Nick Koston
29f8d70b35 [thermostat] Avoid heap allocation for triggers (#13692) 2026-02-02 04:41:08 +01:00
Simon Fischer
1ff2f3b6a3 [dlms_meter] Add dlms smart meter component (#8009)
Co-authored-by: Thomas Rupprecht <rupprecht.thomas@gmail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 10:48:27 -05:00
Jonathan Swoboda
891382a32e [max7219] Allocate buffer in constructor (#13660)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 09:59:13 -05:00
J. Nick Koston
0fd50b2381 [esp32] Disable unused per-tag log filtering, saving ~536 bytes RAM (#13662) 2026-01-31 01:21:52 -06:00
Clyde Stubbs
9dcb469460 [core] Simplify generation of Lambda during to_code() (#13533) 2026-01-31 12:18:30 +11:00
J0k3r2k1
5e3561d60b [mipi_spi] Fix log_pin() FlashStringHelper compatibility (#13624)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-30 14:33:45 -06:00
Thomas Rupprecht
ca9ed369f9 [pmsx003] support device-types PMS1003, PMS3003, PMS9003M (#13640) 2026-01-30 14:59:47 -05:00
J. Nick Koston
4e96b20b46 [mqtt] Restore ESP8266 on_message defer to prevent stack overflow (#13648) 2026-01-30 12:49:14 -06:00
J. Nick Koston
a1a60c44da [web_server_base] Update ESPAsyncWebServer to 3.9.6 (#13639) 2026-01-30 12:48:34 -06:00
Shivam Maurya
898c8a5836 [core] ESP32 chip revision text (#13647) 2026-01-30 11:01:00 -05:00
639 changed files with 10448 additions and 7426 deletions

View File

@@ -1 +1 @@
cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab
37ec8d5a343c8d0a485fd2118cbdabcbccd7b9bca197e4a392be75087974dced

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
category: "/language:${{matrix.language}}"

View File

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

View File

@@ -134,6 +134,7 @@ esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber
esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68
esphome/components/dlms_meter/* @SimonFischer04
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
@@ -531,7 +532,7 @@ esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
esphome/components/ultrasonic/* @ssieb @swoboda1337
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_cdc_acm/* @kbx81

View File

@@ -294,8 +294,13 @@ def has_api() -> bool:
def has_ota() -> bool:
"""Check if OTA is available."""
return CONF_OTA in CORE.config
"""Check if OTA upload is available (requires platform: esphome)."""
if CONF_OTA not in CORE.config:
return False
return any(
ota_item.get(CONF_PLATFORM) == CONF_ESPHOME
for ota_item in CORE.config[CONF_OTA]
)
def has_mqtt_ip_lookup() -> bool:

View File

@@ -12,7 +12,6 @@ from .const import (
CORE_SUBCATEGORY_PATTERNS,
DEMANGLED_PATTERNS,
ESPHOME_COMPONENT_PATTERN,
SECTION_TO_ATTR,
SYMBOL_PATTERNS,
)
from .demangle import batch_demangle
@@ -44,6 +43,7 @@ _READELF_SECTION_PATTERN = re.compile(
# Component category prefixes
_COMPONENT_PREFIX_ESPHOME = "[esphome]"
_COMPONENT_PREFIX_EXTERNAL = "[external]"
_COMPONENT_PREFIX_LIB = "[lib]"
_COMPONENT_CORE = f"{_COMPONENT_PREFIX_ESPHOME}core"
_COMPONENT_API = f"{_COMPONENT_PREFIX_ESPHOME}api"
@@ -57,6 +57,16 @@ SymbolInfoType = tuple[str, int, str]
# RAM sections - symbols in these sections consume RAM
RAM_SECTIONS = frozenset([".data", ".bss"])
# nm symbol types for global/weak defined symbols (used for library symbol mapping)
# Only global (uppercase) and weak symbols are safe to use - local symbols (lowercase)
# can have name collisions across compilation units
_NM_DEFINED_GLOBAL_TYPES = frozenset({"T", "D", "B", "R", "W", "V"})
# Pattern matching compiler-generated local names that can collide across compilation
# units (e.g., packet$19, buf$20, flag$5261). These are unsafe for name-based lookup.
# Does NOT match mangled C++ names with optimization suffixes (e.g., func$isra$0).
_COMPILER_LOCAL_PATTERN = re.compile(r"^[a-zA-Z_]\w*\$\d+$")
@dataclass
class MemorySection:
@@ -91,6 +101,17 @@ class ComponentMemory:
bss_size: int = 0 # Uninitialized data (ram only)
symbol_count: int = 0
def add_section_size(self, section_name: str, size: int) -> None:
"""Add size to the appropriate attribute for a section."""
if section_name == ".text":
self.text_size += size
elif section_name == ".rodata":
self.rodata_size += size
elif section_name == ".data":
self.data_size += size
elif section_name == ".bss":
self.bss_size += size
@property
def flash_total(self) -> int:
"""Total flash usage (text + rodata + data)."""
@@ -167,12 +188,23 @@ class MemoryAnalyzer:
self._elf_symbol_names: set[str] = set()
# SDK symbols not in ELF (static/local symbols from closed-source libs)
self._sdk_symbols: list[SDKSymbol] = []
# CSWTCH symbols: list of (name, size, source_file, component)
self._cswtch_symbols: list[tuple[str, int, str, str]] = []
# Library symbol mapping: symbol_name -> library_name
self._lib_symbol_map: dict[str, str] = {}
# Library dir to name mapping: "lib641" -> "espsoftwareserial",
# "espressif__mdns" -> "mdns"
self._lib_hash_to_name: dict[str, str] = {}
# Heuristic category to library redirect: "mdns_lib" -> "[lib]mdns"
self._heuristic_to_lib: dict[str, str] = {}
def analyze(self) -> dict[str, ComponentMemory]:
"""Analyze the ELF file and return component memory usage."""
self._parse_sections()
self._parse_symbols()
self._scan_libraries()
self._categorize_symbols()
self._analyze_cswtch_symbols()
self._analyze_sdk_libraries()
return dict(self.components)
@@ -255,8 +287,7 @@ class MemoryAnalyzer:
comp_mem.symbol_count += 1
# Update the appropriate size attribute based on section
if attr_name := SECTION_TO_ATTR.get(section_name):
setattr(comp_mem, attr_name, getattr(comp_mem, attr_name) + size)
comp_mem.add_section_size(section_name, size)
# Track uncategorized symbols
if component == "other" and size > 0:
@@ -316,15 +347,19 @@ class MemoryAnalyzer:
# If no component match found, it's core
return _COMPONENT_CORE
# Check library symbol map (more accurate than heuristic patterns)
if lib_name := self._lib_symbol_map.get(symbol_name):
return f"{_COMPONENT_PREFIX_LIB}{lib_name}"
# Check against symbol patterns
for component, patterns in SYMBOL_PATTERNS.items():
if any(pattern in symbol_name for pattern in patterns):
return component
return self._heuristic_to_lib.get(component, component)
# Check against demangled patterns
for component, patterns in DEMANGLED_PATTERNS.items():
if any(pattern in demangled for pattern in patterns):
return component
return self._heuristic_to_lib.get(component, component)
# Special cases that need more complex logic
@@ -372,6 +407,610 @@ class MemoryAnalyzer:
return "Other Core"
def _discover_pio_libraries(
self,
libraries: dict[str, list[Path]],
hash_to_name: dict[str, str],
) -> None:
"""Discover PlatformIO third-party libraries from the build directory.
Scans ``lib<hex>/`` directories under ``.pioenvs/<env>/`` to find
library names and their ``.a`` archive or ``.o`` file paths.
Args:
libraries: Dict to populate with library name -> file path list mappings.
Prefers ``.a`` archives when available, falls back to ``.o`` files
(e.g., pioarduino ESP32 Arduino builds only produce ``.o`` files).
hash_to_name: Dict to populate with dir name -> library name mappings
for CSWTCH attribution (e.g., ``lib641`` -> ``espsoftwareserial``).
"""
build_dir = self.elf_path.parent
for entry in build_dir.iterdir():
if not entry.is_dir() or not entry.name.startswith("lib"):
continue
# Validate that the suffix after "lib" is a hex hash
hex_part = entry.name[3:]
if not hex_part:
continue
try:
int(hex_part, 16)
except ValueError:
continue
# Each lib<hex>/ directory contains a subdirectory named after the library
for lib_subdir in entry.iterdir():
if not lib_subdir.is_dir():
continue
lib_name = lib_subdir.name.lower()
# Prefer .a archive (lib<LibraryName>.a), fall back to .o files
# e.g., lib72a/ESPAsyncTCP/... has lib72a/libESPAsyncTCP.a
archive = entry / f"lib{lib_subdir.name}.a"
if archive.exists():
file_paths = [archive]
elif archives := list(entry.glob("*.a")):
# Case-insensitive fallback
file_paths = [archives[0]]
else:
# No .a archive (e.g., pioarduino CMake builds) - use .o files
file_paths = sorted(lib_subdir.rglob("*.o"))
if file_paths:
libraries[lib_name] = file_paths
hash_to_name[entry.name] = lib_name
_LOGGER.debug(
"Discovered PlatformIO library: %s -> %s",
lib_subdir.name,
file_paths[0],
)
def _discover_idf_managed_components(
self,
libraries: dict[str, list[Path]],
hash_to_name: dict[str, str],
) -> None:
"""Discover ESP-IDF managed component libraries from the build directory.
ESP-IDF managed components (from the IDF component registry) use a
``<vendor>__<name>`` naming convention. Source files live under
``managed_components/<vendor>__<name>/`` and the compiled archives are at
``esp-idf/<vendor>__<name>/lib<vendor>__<name>.a``.
Args:
libraries: Dict to populate with library name -> file path list mappings.
hash_to_name: Dict to populate with dir name -> library name mappings
for CSWTCH attribution (e.g., ``espressif__mdns`` -> ``mdns``).
"""
build_dir = self.elf_path.parent
managed_dir = build_dir / "managed_components"
if not managed_dir.is_dir():
return
espidf_dir = build_dir / "esp-idf"
for entry in managed_dir.iterdir():
if not entry.is_dir() or "__" not in entry.name:
continue
# Extract the short name: espressif__mdns -> mdns
full_name = entry.name # e.g., espressif__mdns
short_name = full_name.split("__", 1)[1].lower()
# Find the .a archive under esp-idf/<vendor>__<name>/
archive = espidf_dir / full_name / f"lib{full_name}.a"
if archive.exists():
libraries[short_name] = [archive]
hash_to_name[full_name] = short_name
_LOGGER.debug(
"Discovered IDF managed component: %s -> %s",
short_name,
archive,
)
def _build_library_symbol_map(
self, libraries: dict[str, list[Path]]
) -> dict[str, str]:
"""Build a symbol-to-library mapping from library archives or object files.
Runs ``nm --defined-only`` on each ``.a`` or ``.o`` file to collect
global and weak defined symbols.
Args:
libraries: Dictionary mapping library name to list of file paths
(``.a`` archives or ``.o`` object files).
Returns:
Dictionary mapping symbol name to library name.
"""
symbol_map: dict[str, str] = {}
if not self.nm_path:
return symbol_map
for lib_name, file_paths in libraries.items():
result = run_tool(
[self.nm_path, "--defined-only", *(str(p) for p in file_paths)],
timeout=10,
)
if result is None or result.returncode != 0:
continue
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) < 3:
continue
sym_type = parts[-2]
sym_name = parts[-1]
# Include global defined symbols (uppercase) and weak symbols (W/V)
if sym_type in _NM_DEFINED_GLOBAL_TYPES:
symbol_map[sym_name] = lib_name
return symbol_map
@staticmethod
def _build_heuristic_to_lib_mapping(
library_names: set[str],
) -> dict[str, str]:
"""Build mapping from heuristic pattern categories to discovered libraries.
Heuristic categories like ``mdns_lib``, ``web_server_lib``, ``async_tcp``
exist as approximations for library attribution. When we discover the
actual library, symbols matching those heuristics should be redirected
to the ``[lib]`` category instead.
The mapping is built by checking if the normalized category name
(stripped of ``_lib`` suffix and underscores) appears as a substring
of any discovered library name.
Examples::
mdns_lib -> mdns -> in "mdns" or "esp8266mdns" -> [lib]mdns
web_server_lib -> webserver -> in "espasyncwebserver" -> [lib]espasyncwebserver
async_tcp -> asynctcp -> in "espasynctcp" -> [lib]espasynctcp
Args:
library_names: Set of discovered library names (lowercase).
Returns:
Dictionary mapping heuristic category to ``[lib]<name>`` string.
"""
mapping: dict[str, str] = {}
all_categories = set(SYMBOL_PATTERNS) | set(DEMANGLED_PATTERNS)
for category in all_categories:
base = category.removesuffix("_lib").replace("_", "")
# Collect all libraries whose name contains the base string
candidates = [lib_name for lib_name in library_names if base in lib_name]
if not candidates:
continue
# Choose a deterministic "best" match:
# 1. Prefer exact name matches over substring matches.
# 2. Among non-exact matches, prefer the shortest library name.
# 3. Break remaining ties lexicographically.
best_lib = min(
candidates,
key=lambda lib_name, _base=base: (
lib_name != _base,
len(lib_name),
lib_name,
),
)
mapping[category] = f"{_COMPONENT_PREFIX_LIB}{best_lib}"
if mapping:
_LOGGER.debug(
"Heuristic-to-library redirects: %s",
", ".join(f"{k} -> {v}" for k, v in sorted(mapping.items())),
)
return mapping
def _parse_map_file(self) -> dict[str, str] | None:
"""Parse linker map file to build authoritative symbol-to-library mapping.
The linker map file contains the definitive source attribution for every
symbol, including local/static ones that ``nm`` cannot safely export.
Map file format (GNU ld)::
.text._mdns_service_task
0x400e9fdc 0x65c .pioenvs/env/esp-idf/espressif__mdns/libespressif__mdns.a(mdns.c.o)
Each section entry has a ``.section.symbol_name`` line followed by an
indented line with address, size, and source path.
Returns:
Symbol-to-library dict, or ``None`` if no usable map file exists.
"""
map_path = self.elf_path.with_suffix(".map")
if not map_path.exists() or map_path.stat().st_size < 10000:
return None
_LOGGER.info("Parsing linker map file: %s", map_path.name)
try:
map_text = map_path.read_text(encoding="utf-8", errors="replace")
except OSError as err:
_LOGGER.warning("Failed to read map file: %s", err)
return None
symbol_map: dict[str, str] = {}
current_symbol: str | None = None
section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.")
for line in map_text.splitlines():
# Match section.symbol line: " .text.symbol_name"
# Single space indent, starts with dot
if len(line) > 2 and line[0] == " " and line[1] == ".":
stripped = line.strip()
for prefix in section_prefixes:
if stripped.startswith(prefix):
current_symbol = stripped[len(prefix) :]
break
else:
current_symbol = None
continue
# Match source attribution line: " 0xADDR 0xSIZE source_path"
if current_symbol is None:
continue
fields = line.split()
# Skip compiler-generated local names (e.g., packet$19, buf$20)
# that can collide across compilation units
if (
len(fields) >= 3
and fields[0].startswith("0x")
and fields[1].startswith("0x")
and not _COMPILER_LOCAL_PATTERN.match(current_symbol)
):
source_path = fields[2]
# Check if source path contains a known library directory
for dir_key, lib_name in self._lib_hash_to_name.items():
if dir_key in source_path:
symbol_map[current_symbol] = lib_name
break
current_symbol = None
return symbol_map or None
def _scan_libraries(self) -> None:
"""Discover third-party libraries and build symbol mapping.
Scans both PlatformIO ``lib<hex>/`` directories (Arduino builds) and
ESP-IDF ``managed_components/`` (IDF builds) to find library archives.
Uses the linker map file for authoritative symbol attribution when
available, falling back to ``nm`` scanning with heuristic redirects.
"""
libraries: dict[str, list[Path]] = {}
self._discover_pio_libraries(libraries, self._lib_hash_to_name)
self._discover_idf_managed_components(libraries, self._lib_hash_to_name)
if not libraries:
_LOGGER.debug("No third-party libraries found")
return
_LOGGER.info(
"Scanning %d libraries: %s",
len(libraries),
", ".join(sorted(libraries)),
)
# Heuristic redirect catches local symbols (e.g., mdns_task_buffer$14)
# that can't be safely added to the symbol map due to name collisions
self._heuristic_to_lib = self._build_heuristic_to_lib_mapping(
set(libraries.keys())
)
# Try linker map file first (authoritative, includes local symbols)
map_symbols = self._parse_map_file()
if map_symbols is not None:
self._lib_symbol_map = map_symbols
_LOGGER.info(
"Built library symbol map from linker map: %d symbols",
len(self._lib_symbol_map),
)
return
# Fall back to nm scanning (global symbols only)
self._lib_symbol_map = self._build_library_symbol_map(libraries)
_LOGGER.info(
"Built library symbol map from nm: %d symbols from %d libraries",
len(self._lib_symbol_map),
len(libraries),
)
def _find_object_files_dir(self) -> Path | None:
"""Find the directory containing object files for this build.
Returns:
Path to the directory containing .o files, or None if not found.
"""
# The ELF is typically at .pioenvs/<env>/firmware.elf
# Object files are in .pioenvs/<env>/src/ and .pioenvs/<env>/lib*/
pioenvs_dir = self.elf_path.parent
if pioenvs_dir.exists() and any(pioenvs_dir.glob("src/*.o")):
return pioenvs_dir
return None
@staticmethod
def _parse_nm_cswtch_output(
output: str,
base_dir: Path | None,
cswtch_map: dict[str, list[tuple[str, int]]],
) -> None:
"""Parse nm output for CSWTCH symbols and add to cswtch_map.
Handles both ``.o`` files and ``.a`` archives.
nm output formats::
.o files: /path/file.o:hex_addr hex_size type name
.a files: /path/lib.a:member.o:hex_addr hex_size type name
For ``.o`` files, paths are made relative to *base_dir* when possible.
For ``.a`` archives (detected by ``:`` in the file portion), paths are
formatted as ``archive_stem/member.o`` (e.g. ``liblwip2-536-feat/lwip-esp.o``).
Args:
output: Raw stdout from ``nm --print-file-name -S``.
base_dir: Base directory for computing relative paths of ``.o`` files.
Pass ``None`` when scanning archives outside the build tree.
cswtch_map: Dict to populate, mapping ``"CSWTCH$N:size"`` to source list.
"""
for line in output.splitlines():
if "CSWTCH$" not in line:
continue
# Split on last ":" that precedes a hex address.
# For .o: "filepath.o" : "hex_addr hex_size type name"
# For .a: "filepath.a:member.o" : "hex_addr hex_size type name"
parts_after_colon = line.rsplit(":", 1)
if len(parts_after_colon) != 2:
continue
file_path = parts_after_colon[0]
fields = parts_after_colon[1].split()
# fields: [address, size, type, name]
if len(fields) < 4:
continue
sym_name = fields[3]
if not sym_name.startswith("CSWTCH$"):
continue
try:
size = int(fields[1], 16)
except ValueError:
continue
# Determine readable source path
# Use ".a:" to detect archive format (not bare ":" which matches
# Windows drive letters like "C:\...\file.o").
if ".a:" in file_path:
# Archive format: "archive.a:member.o" → "archive_stem/member.o"
archive_part, member = file_path.rsplit(":", 1)
archive_name = Path(archive_part).stem
rel_path = f"{archive_name}/{member}"
elif base_dir is not None:
try:
rel_path = str(Path(file_path).relative_to(base_dir))
except ValueError:
rel_path = file_path
else:
rel_path = file_path
key = f"{sym_name}:{size}"
cswtch_map[key].append((rel_path, size))
def _run_nm_cswtch_scan(
self,
files: list[Path],
base_dir: Path | None,
cswtch_map: dict[str, list[tuple[str, int]]],
) -> None:
"""Run nm on *files* and add any CSWTCH symbols to *cswtch_map*.
Args:
files: Object (``.o``) or archive (``.a``) files to scan.
base_dir: Base directory for relative path computation (see
:meth:`_parse_nm_cswtch_output`).
cswtch_map: Dict to populate with results.
"""
if not self.nm_path or not files:
return
_LOGGER.debug("Scanning %d files for CSWTCH symbols", len(files))
result = run_tool(
[self.nm_path, "--print-file-name", "-S"] + [str(f) for f in files],
timeout=30,
)
if result is None or result.returncode != 0:
_LOGGER.debug(
"nm failed or timed out scanning %d files for CSWTCH symbols",
len(files),
)
return
self._parse_nm_cswtch_output(result.stdout, base_dir, cswtch_map)
def _scan_cswtch_in_sdk_archives(
self, cswtch_map: dict[str, list[tuple[str, int]]]
) -> None:
"""Scan SDK library archives (.a) for CSWTCH symbols.
Prebuilt SDK libraries (e.g. lwip, bearssl) are not compiled from source,
so their CSWTCH symbols only exist inside ``.a`` archives. Results are
merged into *cswtch_map* for keys not already found in ``.o`` files.
The same source file (e.g. ``lwip-esp.o``) often appears in multiple
library variants (``liblwip2-536.a``, ``liblwip2-1460-feat.a``, etc.),
so results are deduplicated by member name.
"""
sdk_dirs = self._find_sdk_library_dirs()
if not sdk_dirs:
return
sdk_archives = sorted(a for sdk_dir in sdk_dirs for a in sdk_dir.glob("*.a"))
sdk_map: dict[str, list[tuple[str, int]]] = defaultdict(list)
self._run_nm_cswtch_scan(sdk_archives, None, sdk_map)
# Merge SDK results, deduplicating by member name.
for key, sources in sdk_map.items():
if key in cswtch_map:
continue
seen: dict[str, tuple[str, int]] = {}
for path, sz in sources:
member = Path(path).name
if member not in seen:
seen[member] = (path, sz)
cswtch_map[key] = list(seen.values())
def _source_file_to_component(self, source_file: str) -> str:
"""Map a source object file path to its component name.
Args:
source_file: Relative path like 'src/esphome/components/wifi/wifi_component.cpp.o'
Returns:
Component name like '[esphome]wifi' or the source file if unknown.
"""
parts = Path(source_file).parts
# ESPHome component: src/esphome/components/<name>/...
if "components" in parts:
idx = parts.index("components")
if idx + 1 < len(parts):
component_name = parts[idx + 1]
if component_name in get_esphome_components():
return f"{_COMPONENT_PREFIX_ESPHOME}{component_name}"
if component_name in self.external_components:
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
# ESPHome core: src/esphome/core/... or src/esphome/...
if "core" in parts and "esphome" in parts:
return _COMPONENT_CORE
if "esphome" in parts and "components" not in parts:
return _COMPONENT_CORE
# Framework/library files - check for PlatformIO library hash dirs
# e.g., lib65b/ESPAsyncTCP/... -> [lib]espasynctcp
if parts and parts[0] in self._lib_hash_to_name:
return f"{_COMPONENT_PREFIX_LIB}{self._lib_hash_to_name[parts[0]]}"
# ESP-IDF managed components: managed_components/espressif__mdns/... -> [lib]mdns
if (
len(parts) >= 2
and parts[0] == "managed_components"
and parts[1] in self._lib_hash_to_name
):
return f"{_COMPONENT_PREFIX_LIB}{self._lib_hash_to_name[parts[1]]}"
# Other framework/library files - return the first path component
# e.g., FrameworkArduino/... -> FrameworkArduino
return parts[0] if parts else source_file
def _analyze_cswtch_symbols(self) -> None:
"""Analyze CSWTCH (GCC switch table) symbols by tracing to source objects.
CSWTCH symbols are compiler-generated lookup tables for switch statements.
They are local symbols, so the same name can appear in different object files.
This method scans .o files and SDK archives to attribute them to their
source components.
"""
obj_dir = self._find_object_files_dir()
if obj_dir is None:
_LOGGER.debug("No object files directory found, skipping CSWTCH analysis")
return
# Scan build-dir object files for CSWTCH symbols
cswtch_map: dict[str, list[tuple[str, int]]] = defaultdict(list)
self._run_nm_cswtch_scan(sorted(obj_dir.rglob("*.o")), obj_dir, cswtch_map)
# Also scan SDK library archives (.a) for CSWTCH symbols.
# Prebuilt SDK libraries (e.g. lwip, bearssl) are not compiled from source
# so their symbols only exist inside .a archives, not as loose .o files.
self._scan_cswtch_in_sdk_archives(cswtch_map)
if not cswtch_map:
_LOGGER.debug("No CSWTCH symbols found in object files or SDK archives")
return
# Collect CSWTCH symbols from the ELF (already parsed in sections)
# Include section_name for re-attribution of component totals
elf_cswtch = [
(symbol_name, size, section_name)
for section_name, section in self.sections.items()
for symbol_name, size, _ in section.symbols
if symbol_name.startswith("CSWTCH$")
]
_LOGGER.debug(
"Found %d CSWTCH symbols in ELF, %d unique in object files",
len(elf_cswtch),
len(cswtch_map),
)
# Match ELF CSWTCH symbols to source files and re-attribute component totals.
# _categorize_symbols() already ran and put these into "other" since CSWTCH$
# names don't match any component pattern. We move the bytes to the correct
# component based on the object file mapping.
other_mem = self.components.get("other")
for sym_name, size, section_name in elf_cswtch:
key = f"{sym_name}:{size}"
sources = cswtch_map.get(key, [])
if len(sources) == 1:
source_file = sources[0][0]
component = self._source_file_to_component(source_file)
elif len(sources) > 1:
# Ambiguous - multiple object files have same CSWTCH name+size
source_file = "ambiguous"
component = "ambiguous"
_LOGGER.debug(
"Ambiguous CSWTCH %s (%d B) found in %d files: %s",
sym_name,
size,
len(sources),
", ".join(src for src, _ in sources),
)
else:
source_file = "unknown"
component = "unknown"
self._cswtch_symbols.append((sym_name, size, source_file, component))
# Re-attribute from "other" to the correct component
if (
component not in ("other", "unknown", "ambiguous")
and other_mem is not None
):
other_mem.add_section_size(section_name, -size)
if component not in self.components:
self.components[component] = ComponentMemory(component)
self.components[component].add_section_size(section_name, size)
# Sort by size descending
self._cswtch_symbols.sort(key=lambda x: x[1], reverse=True)
total_size = sum(size for _, size, _, _ in self._cswtch_symbols)
_LOGGER.debug(
"CSWTCH analysis: %d symbols, %d bytes total",
len(self._cswtch_symbols),
total_size,
)
def get_unattributed_ram(self) -> tuple[int, int, int]:
"""Get unattributed RAM sizes (SDK/framework overhead).

View File

@@ -4,6 +4,8 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable
import heapq
from operator import itemgetter
import sys
from typing import TYPE_CHECKING
@@ -12,6 +14,7 @@ from . import (
_COMPONENT_CORE,
_COMPONENT_PREFIX_ESPHOME,
_COMPONENT_PREFIX_EXTERNAL,
_COMPONENT_PREFIX_LIB,
RAM_SECTIONS,
MemoryAnalyzer,
)
@@ -29,6 +32,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
)
# Lower threshold for RAM symbols (RAM is more constrained)
RAM_SYMBOL_SIZE_THRESHOLD: int = 24
# Number of top symbols to show in the largest symbols report
TOP_SYMBOLS_LIMIT: int = 30
# Width for symbol name display in top symbols report
COL_TOP_SYMBOL_NAME: int = 55
# Column width constants
COL_COMPONENT: int = 29
@@ -147,6 +154,83 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
return f"{demangled} ({size:,} B){section_label}"
def _add_top_symbols(self, lines: list[str]) -> None:
"""Add a section showing the top largest symbols in the binary."""
# Collect all symbols from all components: (symbol, demangled, size, section, component)
all_symbols = [
(symbol, demangled, size, section, component)
for component, symbols in self._component_symbols.items()
for symbol, demangled, size, section in symbols
]
# Get top N symbols by size using heapq for efficiency
top_symbols = heapq.nlargest(
self.TOP_SYMBOLS_LIMIT, all_symbols, key=itemgetter(2)
)
lines.append("")
lines.append(f"Top {self.TOP_SYMBOLS_LIMIT} Largest Symbols:")
# Calculate truncation limit from column width (leaving room for "...")
truncate_limit = self.COL_TOP_SYMBOL_NAME - 3
for i, (_, demangled, size, section, component) in enumerate(top_symbols):
# Format section label
section_label = f"[{section[1:]}]" if section else ""
# Truncate demangled name if too long
demangled_display = (
f"{demangled[:truncate_limit]}..."
if len(demangled) > self.COL_TOP_SYMBOL_NAME
else demangled
)
lines.append(
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
)
def _add_cswtch_analysis(self, lines: list[str]) -> None:
"""Add CSWTCH (GCC switch table lookup) analysis section."""
self._add_section_header(lines, "CSWTCH Analysis (GCC Switch Table Lookups)")
total_size = sum(size for _, size, _, _ in self._cswtch_symbols)
lines.append(
f"Total: {len(self._cswtch_symbols)} switch table(s), {total_size:,} B"
)
lines.append("")
# Group by component
by_component: dict[str, list[tuple[str, int, str]]] = defaultdict(list)
for sym_name, size, source_file, component in self._cswtch_symbols:
by_component[component].append((sym_name, size, source_file))
# Sort components by total size descending
sorted_components = sorted(
by_component.items(),
key=lambda x: sum(s[1] for s in x[1]),
reverse=True,
)
for component, symbols in sorted_components:
comp_total = sum(s[1] for s in symbols)
lines.append(f"{component} ({comp_total:,} B, {len(symbols)} tables):")
# Group by source file within component
by_file: dict[str, list[tuple[str, int]]] = defaultdict(list)
for sym_name, size, source_file in symbols:
by_file[source_file].append((sym_name, size))
for source_file, file_symbols in sorted(
by_file.items(),
key=lambda x: sum(s[1] for s in x[1]),
reverse=True,
):
file_total = sum(s[1] for s in file_symbols)
lines.append(
f" {source_file} ({file_total:,} B, {len(file_symbols)} tables)"
)
for sym_name, size in sorted(
file_symbols, key=lambda x: x[1], reverse=True
):
lines.append(f" {size:>6,} B {sym_name}")
lines.append("")
def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report."""
components = sorted(
@@ -248,6 +332,9 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
"RAM",
)
# Top largest symbols in the binary
self._add_top_symbols(lines)
# Add ESPHome core detailed analysis if there are core symbols
if self._esphome_core_symbols:
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")
@@ -321,6 +408,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
for name, mem in components
if name.startswith(_COMPONENT_PREFIX_EXTERNAL)
]
library_components = [
(name, mem)
for name, mem in components
if name.startswith(_COMPONENT_PREFIX_LIB)
]
top_esphome_components = sorted(
esphome_components, key=lambda x: x[1].flash_total, reverse=True
@@ -331,6 +423,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
external_components, key=lambda x: x[1].flash_total, reverse=True
)
# Include all library components
top_library_components = sorted(
library_components, key=lambda x: x[1].flash_total, reverse=True
)
# Check if API component exists and ensure it's included
api_component = None
for name, mem in components:
@@ -349,10 +446,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
if name in system_components_to_include
]
# Combine all components to analyze: top ESPHome + all external + API if not already included + system components
# Combine all components to analyze: top ESPHome + all external + libraries + API if not already included + system components
components_to_analyze = (
list(top_esphome_components)
+ list(top_external_components)
+ list(top_library_components)
+ system_components
)
if api_component and api_component not in components_to_analyze:
@@ -431,6 +529,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
lines.append(f" ... and {len(large_ram_syms) - 10} more")
lines.append("")
# CSWTCH (GCC switch table) analysis
if self._cswtch_symbols:
self._add_cswtch_analysis(lines)
lines.append(
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
)

View File

@@ -66,15 +66,6 @@ SECTION_MAPPING = {
),
}
# Section to ComponentMemory attribute mapping
# Maps section names to the attribute name in ComponentMemory dataclass
SECTION_TO_ATTR = {
".text": "text_size",
".rodata": "rodata_size",
".data": "data_size",
".bss": "bss_size",
}
# Component identification rules
# Symbol patterns: patterns found in raw symbol names
SYMBOL_PATTERNS = {
@@ -513,7 +504,9 @@ SYMBOL_PATTERNS = {
"__FUNCTION__$",
"DAYS_IN_MONTH",
"_DAYS_BEFORE_MONTH",
"CSWTCH$",
# Note: CSWTCH$ symbols are GCC switch table lookup tables.
# They are attributed to their source object files via _analyze_cswtch_symbols()
# rather than being lumped into libc.
"dst$",
"sulp",
"_strtol_l", # String to long with locale

View File

@@ -87,6 +87,7 @@ from esphome.cpp_types import ( # noqa: F401
size_t,
std_ns,
std_shared_ptr,
std_span,
std_string,
std_string_ref,
std_vector,

View File

@@ -45,8 +45,6 @@ void AbsoluteHumidityComponent::dump_config() {
this->temperature_sensor_->get_name().c_str(), this->humidity_sensor_->get_name().c_str());
}
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }
void AbsoluteHumidityComponent::loop() {
if (!this->next_update_) {
return;

View File

@@ -24,7 +24,6 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
protected:

View File

@@ -68,11 +68,6 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// This method is called during the ESPHome setup process to log the configuration.
void dump_config() override;
/// Return the setup priority for this component.
/// Components with higher priority are initialized earlier during setup.
/// @return A float representing the setup priority.
float get_setup_priority() const override;
#ifdef USE_ZEPHYR
/// Set the ADC channel to be used by the ADC sensor.
/// @param channel Pointer to an adc_dt_spec structure representing the ADC channel.

View File

@@ -79,7 +79,5 @@ void ADCSensor::set_sample_count(uint8_t sample_count) {
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace adc
} // namespace esphome

View File

@@ -42,11 +42,11 @@ void ADCSensor::setup() {
adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
init_config.unit_id = this->adc_unit_;
init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
#if USE_ESP32_VARIANT_ESP32C2 || USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || \
USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
init_config.clk_src = ADC_DIGI_CLK_SRC_DEFAULT;
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 ||
// USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
#endif // USE_ESP32_VARIANT_ESP32C2 || USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 ||
// USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
esp_err_t err = adc_oneshot_new_unit(&init_config, &ADCSensor::shared_adc_handles[this->adc_unit_]);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing %s: %d", LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), err);
@@ -76,7 +76,7 @@ void ADCSensor::setup() {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
// RISC-V variants and S3 use curve fitting calibration
// RISC-V variants (except C2) and S3 use curve fitting calibration
adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
cali_config.chan = this->channel_;
@@ -94,14 +94,14 @@ void ADCSensor::setup() {
ESP_LOGW(TAG, "Curve fitting calibration failed with error %d, will use uncalibrated readings", err);
this->setup_flags_.calibration_complete = false;
}
#else // Other ESP32 variants use line fitting calibration
#else // ESP32, ESP32-S2, and ESP32-C2 use line fitting calibration
adc_cali_line_fitting_config_t cali_config = {
.unit_id = this->adc_unit_,
.atten = this->attenuation_,
.bitwidth = ADC_BITWIDTH_DEFAULT,
#if !defined(USE_ESP32_VARIANT_ESP32S2)
#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
.default_vref = 1100, // Default reference voltage in mV
#endif // !defined(USE_ESP32_VARIANT_ESP32S2)
#endif // !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
};
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (err == ESP_OK) {
@@ -112,7 +112,7 @@ void ADCSensor::setup() {
ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err);
this->setup_flags_.calibration_complete = false;
}
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
#endif // ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
}
this->setup_flags_.init_complete = true;
@@ -189,7 +189,7 @@ float ADCSensor::sample_fixed_attenuation_() {
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else // Other ESP32 variants use line fitting calibration
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
#endif // ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
this->calibration_handle_ = nullptr;
}
}
@@ -247,7 +247,7 @@ float ADCSensor::sample_autorange_() {
.unit_id = this->adc_unit_,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
#if !defined(USE_ESP32_VARIANT_ESP32S2)
#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
.default_vref = 1100,
#endif
};

View File

@@ -9,8 +9,6 @@ static const char *const TAG = "adc128s102.sensor";
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; }
void ADC128S102Sensor::dump_config() {
LOG_SENSOR("", "ADC128S102 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->channel_);

View File

@@ -19,7 +19,6 @@ class ADC128S102Sensor : public PollingComponent,
void update() override;
void dump_config() override;
float get_setup_priority() const override;
float sample() override;
protected:

View File

@@ -150,8 +150,6 @@ void AHT10Component::update() {
this->restart_read_();
}
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
void AHT10Component::dump_config() {
ESP_LOGCONFIG(TAG, "AHT10:");
LOG_I2C_DEVICE(this);

View File

@@ -16,7 +16,6 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
void setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override;
void set_variant(AHT10Variant variant) { this->variant_ = variant; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -1,32 +1,15 @@
#include "alarm_control_panel_state.h"
#include "esphome/core/progmem.h"
namespace esphome::alarm_control_panel {
// Alarm control panel state strings indexed by AlarmControlPanelState enum (0-9)
PROGMEM_STRING_TABLE(AlarmControlPanelStateStrings, "DISARMED", "ARMED_HOME", "ARMED_AWAY", "ARMED_NIGHT",
"ARMED_VACATION", "ARMED_CUSTOM_BYPASS", "PENDING", "ARMING", "DISARMING", "TRIGGERED", "UNKNOWN");
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
switch (state) {
case ACP_STATE_DISARMED:
return LOG_STR("DISARMED");
case ACP_STATE_ARMED_HOME:
return LOG_STR("ARMED_HOME");
case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT:
return LOG_STR("ARMED_NIGHT");
case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return LOG_STR("ARMED_CUSTOM_BYPASS");
case ACP_STATE_PENDING:
return LOG_STR("PENDING");
case ACP_STATE_ARMING:
return LOG_STR("ARMING");
case ACP_STATE_DISARMING:
return LOG_STR("DISARMING");
case ACP_STATE_TRIGGERED:
return LOG_STR("TRIGGERED");
default:
return LOG_STR("UNKNOWN");
}
return AlarmControlPanelStateStrings::get_log_str(static_cast<uint8_t>(state),
AlarmControlPanelStateStrings::LAST_INDEX);
}
} // namespace esphome::alarm_control_panel

View File

@@ -176,7 +176,5 @@ void AM2315C::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
float AM2315C::get_setup_priority() const { return setup_priority::DATA; }
} // namespace am2315c
} // namespace esphome

View File

@@ -33,7 +33,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
void dump_config() override;
void update() override;
void setup() override;
float get_setup_priority() const override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }

View File

@@ -51,7 +51,6 @@ void AM2320Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
float AM2320Component::get_setup_priority() const { return setup_priority::DATA; }
bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) {
if (!this->write_bytes(a_register, data, 2)) {

View File

@@ -11,7 +11,6 @@ class AM2320Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -384,7 +384,6 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) {
}
}
}
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
bool APDS9960::is_proximity_enabled_() const {
return
#ifdef USE_SENSOR

View File

@@ -32,7 +32,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void loop() override;

View File

@@ -45,6 +45,7 @@ service APIConnection {
rpc time_command (TimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {}
rpc valve_command (ValveCommandRequest) returns (void) {}
rpc water_heater_command (WaterHeaterCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
class APIConnection final : public APIServerConnection {
class APIConnection final : public APIServerConnectionBase {
public:
friend class APIServer;
friend class ListEntitiesIterator;
@@ -47,72 +47,72 @@ class APIConnection final : public APIServerConnection {
#endif
#ifdef USE_COVER
bool send_cover_state(cover::Cover *cover);
void cover_command(const CoverCommandRequest &msg) override;
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_FAN
bool send_fan_state(fan::Fan *fan);
void fan_command(const FanCommandRequest &msg) override;
void on_fan_command_request(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
bool send_light_state(light::LightState *light);
void light_command(const LightCommandRequest &msg) override;
void on_light_command_request(const LightCommandRequest &msg) override;
#endif
#ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor);
#endif
#ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch);
void switch_command(const SwitchCommandRequest &msg) override;
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
#endif
#ifdef USE_CAMERA
void set_camera_state(std::shared_ptr<camera::CameraImage> image);
void camera_image(const CameraImageRequest &msg) override;
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate);
void climate_command(const ClimateCommandRequest &msg) override;
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
bool send_number_state(number::Number *number);
void number_command(const NumberCommandRequest &msg) override;
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
bool send_date_state(datetime::DateEntity *date);
void date_command(const DateCommandRequest &msg) override;
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_TIME
bool send_time_state(datetime::TimeEntity *time);
void time_command(const TimeCommandRequest &msg) override;
void on_time_command_request(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
bool send_datetime_state(datetime::DateTimeEntity *datetime);
void datetime_command(const DateTimeCommandRequest &msg) override;
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
bool send_text_state(text::Text *text);
void text_command(const TextCommandRequest &msg) override;
void on_text_command_request(const TextCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
bool send_select_state(select::Select *select);
void select_command(const SelectCommandRequest &msg) override;
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
void button_command(const ButtonCommandRequest &msg) override;
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
bool send_lock_state(lock::Lock *a_lock);
void lock_command(const LockCommandRequest &msg) override;
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
bool send_valve_state(valve::Valve *valve);
void valve_command(const ValveCommandRequest &msg) override;
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool send_media_player_state(media_player::MediaPlayer *media_player);
void media_player_command(const MediaPlayerCommandRequest &msg) override;
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES
@@ -126,18 +126,18 @@ class APIConnection final : public APIServerConnection {
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_BLUETOOTH_PROXY
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
void on_unsubscribe_bluetooth_le_advertisements_request() override;
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override;
void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override;
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
bool send_subscribe_bluetooth_connections_free_response(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
void on_subscribe_bluetooth_connections_free_request() override;
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_HOMEASSISTANT_TIME
@@ -148,24 +148,24 @@ class APIConnection final : public APIServerConnection {
#endif
#ifdef USE_VOICE_ASSISTANT
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override;
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) override;
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif
#ifdef USE_ZWAVE_PROXY
void zwave_proxy_frame(const ZWaveProxyFrame &msg) override;
void zwave_proxy_request(const ZWaveProxyRequest &msg) override;
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif
#ifdef USE_WATER_HEATER
@@ -174,7 +174,7 @@ class APIConnection final : public APIServerConnection {
#endif
#ifdef USE_IR_RF
void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override;
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
#endif
@@ -184,11 +184,11 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *update);
void update_command(const UpdateCommandRequest &msg) override;
void on_update_command_request(const UpdateCommandRequest &msg) override;
#endif
void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override {
void on_disconnect_response() override;
void on_ping_response() override {
// we initiated ping
this->flags_.sent_ping = false;
}
@@ -198,12 +198,12 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_HOMEASSISTANT_TIME
void on_get_time_response(const GetTimeResponse &value) override;
#endif
bool send_hello_response(const HelloRequest &msg) override;
bool send_disconnect_response(const DisconnectRequest &msg) override;
bool send_ping_response(const PingRequest &msg) override;
bool send_device_info_response(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void subscribe_states(const SubscribeStatesRequest &msg) override {
void on_hello_request(const HelloRequest &msg) override;
void on_disconnect_request() override;
void on_ping_request() override;
void on_device_info_request() override;
void on_list_entities_request() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void on_subscribe_states_request() override {
this->flags_.state_subscription = true;
// Start initial state iterator only if no iterator is active
// If list_entities is running, we'll start initial_state when it completes
@@ -211,21 +211,19 @@ class APIConnection final : public APIServerConnection {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
}
void subscribe_logs(const SubscribeLogsRequest &msg) override {
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override {
this->flags_.log_subscription = msg.level;
if (msg.dump_config)
App.schedule_dump_config();
}
#ifdef USE_API_HOMEASSISTANT_SERVICES
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
this->flags_.service_call_subscription = true;
}
void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; }
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_subscribe_home_assistant_states_request() override;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
void execute_service(const ExecuteServiceRequest &msg) override;
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
@@ -235,7 +233,7 @@ class APIConnection final : public APIServerConnection {
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
#endif
#ifdef USE_API_NOISE
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override;
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
bool is_authenticated() override {
@@ -255,17 +253,7 @@ class APIConnection final : public APIServerConnection {
void on_fatal_error() override;
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
// Get shared buffer from parent server
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, header_padding,
reserve_size + header_padding + this->helper_->frame_footer_size());
return {&shared_buf};
}
bool send_message_impl(const ProtoMessage &msg, uint8_t message_type) override;
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
shared_buf.clear();
@@ -277,6 +265,13 @@ class APIConnection final : public APIServerConnection {
shared_buf.resize(header_padding);
}
// Convenience overload - computes frame overhead internally
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t payload_size) {
const uint8_t header_padding = this->helper_->frame_header_padding();
const uint8_t footer_size = this->helper_->frame_footer_size();
this->prepare_first_message_buffer(shared_buf, header_padding, payload_size + header_padding + footer_size);
}
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
@@ -288,6 +283,21 @@ class APIConnection final : public APIServerConnection {
// Helper function to handle authentication completion
void complete_authentication_();
// Pattern B helpers: send response and return success/failure
bool send_hello_response_(const HelloRequest &msg);
bool send_disconnect_response_();
bool send_ping_response_();
bool send_device_info_response_();
#ifdef USE_API_NOISE
bool send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_subscribe_bluetooth_connections_free_response_();
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg);
#endif
#ifdef USE_CAMERA
void try_send_camera_image_();
#endif
@@ -298,21 +308,21 @@ class APIConnection final : public APIServerConnection {
// Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
uint32_t remaining_size);
// Helper to fill entity state base and encode message
static uint16_t fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size, bool is_single) {
APIConnection *conn, uint32_t remaining_size) {
msg.key = entity->get_object_id_hash();
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
return encode_message_to_buffer(msg, message_type, conn, remaining_size, is_single);
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
}
// Helper to fill entity info base and encode message
static uint16_t fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size, bool is_single) {
APIConnection *conn, uint32_t remaining_size) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
@@ -339,7 +349,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
return encode_message_to_buffer(msg, message_type, conn, remaining_size, is_single);
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
}
#ifdef USE_VOICE_ASSISTANT
@@ -370,141 +380,108 @@ class APIConnection final : public APIServerConnection {
}
#ifdef USE_BINARY_SENSOR
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_COVER
static uint16_t try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_FAN
static uint16_t try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_LIGHT
static uint16_t try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_SENSOR
static uint16_t try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_SWITCH
static uint16_t try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_TEXT_SENSOR
static uint16_t try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_CLIMATE
static uint16_t try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_NUMBER
static uint16_t try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_DATETIME_DATE
static uint16_t try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_DATETIME_TIME
static uint16_t try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_DATETIME_DATETIME
static uint16_t try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_TEXT
static uint16_t try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_SELECT
static uint16_t try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_BUTTON
static uint16_t try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_LOCK
static uint16_t try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_VALVE
static uint16_t try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_MEDIA_PLAYER
static uint16_t try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
static uint16_t try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_alarm_control_panel_state(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);
#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);
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_INFRARED
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
uint32_t remaining_size);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_UPDATE
static uint16_t try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
#ifdef USE_CAMERA
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
#endif
// Method for ListEntitiesDone batching
static uint16_t try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
// Method for DisconnectRequest batching
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
// Batch message method for ping requests
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
// === Optimal member ordering for 32-bit systems ===
@@ -539,7 +516,7 @@ class APIConnection final : public APIServerConnection {
#endif
// Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size);
// Generic batching mechanism for both state updates and entity info
struct DeferredBatch {
@@ -652,7 +629,7 @@ class APIConnection final : public APIServerConnection {
// Dispatch message encoding based on message_type - replaces function pointer storage
// Switch assigns pointer, single call site for smaller code size
uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool is_single);
uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool batch_first);
#ifdef HAS_PROTO_MESSAGE_DUMP
void log_batch_item_(const DeferredBatch::BatchItem &item) {
@@ -684,19 +661,7 @@ class APIConnection final : public APIServerConnection {
// Tries immediate send if should_send_immediately_() returns true and buffer has space
// Falls back to batching if immediate send fails or isn't applicable
bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_batch_item_(item);
#endif
return true;
}
}
return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
}
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED);
// Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,

View File

@@ -440,19 +440,6 @@ class PingResponse final : public ProtoMessage {
protected:
};
class DeviceInfoRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 9;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "device_info_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
#ifdef USE_AREAS
class AreaInfo final : public ProtoMessage {
public:
@@ -546,19 +533,6 @@ class DeviceInfoResponse final : public ProtoMessage {
protected:
};
class ListEntitiesRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class ListEntitiesDoneResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 19;
@@ -572,19 +546,6 @@ class ListEntitiesDoneResponse final : public ProtoMessage {
protected:
};
class SubscribeStatesRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 20;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_states_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
#ifdef USE_BINARY_SENSOR
class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
public:
@@ -1037,19 +998,6 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
};
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 34;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_homeassistant_services_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class HomeassistantServiceMap final : public ProtoMessage {
public:
StringRef key{};
@@ -1117,19 +1065,6 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 38;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_home_assistant_states_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 39;
@@ -2160,19 +2095,6 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
protected:
};
class SubscribeBluetoothConnectionsFreeRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 80;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_bluetooth_connections_free_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class BluetoothConnectionsFreeResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 81;
@@ -2279,19 +2201,6 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage {
protected:
};
class UnsubscribeBluetoothLEAdvertisementsRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 87;
static constexpr uint8_t ESTIMATED_SIZE = 0;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "unsubscribe_bluetooth_le_advertisements_request"; }
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 88;

View File

@@ -23,15 +23,8 @@ static inline void append_field_prefix(DumpBuffer &out, const char *field_name,
out.append(indent, ' ').append(field_name).append(": ");
}
static inline void append_with_newline(DumpBuffer &out, const char *str) {
out.append(str);
out.append("\n");
}
static inline void append_uint(DumpBuffer &out, uint32_t value) {
char buf[16];
snprintf(buf, sizeof(buf), "%" PRIu32, value);
out.append(buf);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRIu32, value));
}
// RAII helper for message dump formatting
@@ -49,31 +42,23 @@ class MessageDumpHelper {
// Helper functions to reduce code duplication in dump methods
static void dump_field(DumpBuffer &out, const char *field_name, int32_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%" PRId32, value);
append_with_newline(out, buffer);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRId32 "\n", value));
}
static void dump_field(DumpBuffer &out, const char *field_name, uint32_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%" PRIu32, value);
append_with_newline(out, buffer);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRIu32 "\n", value));
}
static void dump_field(DumpBuffer &out, const char *field_name, float value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%g", value);
append_with_newline(out, buffer);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%g\n", value));
}
static void dump_field(DumpBuffer &out, const char *field_name, uint64_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%" PRIu64, value);
append_with_newline(out, buffer);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRIu64 "\n", value));
}
static void dump_field(DumpBuffer &out, const char *field_name, bool value, int indent = 2) {
@@ -112,7 +97,7 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint
char hex_buf[format_hex_pretty_size(160)];
append_field_prefix(out, field_name, indent);
format_hex_pretty_to(hex_buf, data, len);
append_with_newline(out, hex_buf);
out.append(hex_buf).append("\n");
}
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
@@ -779,10 +764,6 @@ const char *PingResponse::dump_to(DumpBuffer &out) const {
out.append("PingResponse {}");
return out.c_str();
}
const char *DeviceInfoRequest::dump_to(DumpBuffer &out) const {
out.append("DeviceInfoRequest {}");
return out.c_str();
}
#ifdef USE_AREAS
const char *AreaInfo::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "AreaInfo");
@@ -863,18 +844,10 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
#endif
return out.c_str();
}
const char *ListEntitiesRequest::dump_to(DumpBuffer &out) const {
out.append("ListEntitiesRequest {}");
return out.c_str();
}
const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const {
out.append("ListEntitiesDoneResponse {}");
return out.c_str();
}
const char *SubscribeStatesRequest::dump_to(DumpBuffer &out) const {
out.append("SubscribeStatesRequest {}");
return out.c_str();
}
#ifdef USE_BINARY_SENSOR
const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
@@ -1206,10 +1179,6 @@ const char *NoiseEncryptionSetKeyResponse::dump_to(DumpBuffer &out) const {
}
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
const char *SubscribeHomeassistantServicesRequest::dump_to(DumpBuffer &out) const {
out.append("SubscribeHomeassistantServicesRequest {}");
return out.c_str();
}
const char *HomeassistantServiceMap::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceMap");
dump_field(out, "key", this->key);
@@ -1260,10 +1229,6 @@ const char *HomeassistantActionResponse::dump_to(DumpBuffer &out) const {
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
const char *SubscribeHomeAssistantStatesRequest::dump_to(DumpBuffer &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}");
return out.c_str();
}
const char *SubscribeHomeAssistantStateResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
dump_field(out, "entity_id", this->entity_id);
@@ -1939,10 +1904,6 @@ const char *BluetoothGATTNotifyDataResponse::dump_to(DumpBuffer &out) const {
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
return out.c_str();
}
const char *SubscribeBluetoothConnectionsFreeRequest::dump_to(DumpBuffer &out) const {
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
return out.c_str();
}
const char *BluetoothConnectionsFreeResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "BluetoothConnectionsFreeResponse");
dump_field(out, "free", this->free);
@@ -1985,10 +1946,6 @@ const char *BluetoothDeviceUnpairingResponse::dump_to(DumpBuffer &out) const {
dump_field(out, "error", this->error);
return out.c_str();
}
const char *UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(DumpBuffer &out) const {
out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}");
return out.c_str();
}
const char *BluetoothDeviceClearCacheResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "BluetoothDeviceClearCacheResponse");
dump_field(out, "address", this->address);

View File

@@ -15,9 +15,29 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name, const
DumpBuffer dump_buf;
ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf));
}
void APIServerConnectionBase::log_receive_message_(const LogString *name) {
ESP_LOGVV(TAG, "%s: {}", LOG_STR_ARG(name));
}
#endif
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required
case DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
break;
case 9 /* DeviceInfoRequest is empty */: // Connection setup only
if (!this->check_connection_setup_()) {
return;
}
break;
default:
if (!this->check_authenticated_()) {
return;
}
break;
}
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: {
HelloRequest msg;
@@ -29,66 +49,52 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_disconnect_request"), msg);
this->log_receive_message_(LOG_STR("on_disconnect_request"));
#endif
this->on_disconnect_request(msg);
this->on_disconnect_request();
break;
}
case DisconnectResponse::MESSAGE_TYPE: {
DisconnectResponse msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_disconnect_response"), msg);
this->log_receive_message_(LOG_STR("on_disconnect_response"));
#endif
this->on_disconnect_response(msg);
this->on_disconnect_response();
break;
}
case PingRequest::MESSAGE_TYPE: {
PingRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_ping_request"), msg);
this->log_receive_message_(LOG_STR("on_ping_request"));
#endif
this->on_ping_request(msg);
this->on_ping_request();
break;
}
case PingResponse::MESSAGE_TYPE: {
PingResponse msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_ping_response"), msg);
this->log_receive_message_(LOG_STR("on_ping_response"));
#endif
this->on_ping_response(msg);
this->on_ping_response();
break;
}
case DeviceInfoRequest::MESSAGE_TYPE: {
DeviceInfoRequest msg;
// Empty message: no decode needed
case 9 /* DeviceInfoRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_device_info_request"), msg);
this->log_receive_message_(LOG_STR("on_device_info_request"));
#endif
this->on_device_info_request(msg);
this->on_device_info_request();
break;
}
case ListEntitiesRequest::MESSAGE_TYPE: {
ListEntitiesRequest msg;
// Empty message: no decode needed
case 11 /* ListEntitiesRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_list_entities_request"), msg);
this->log_receive_message_(LOG_STR("on_list_entities_request"));
#endif
this->on_list_entities_request(msg);
this->on_list_entities_request();
break;
}
case SubscribeStatesRequest::MESSAGE_TYPE: {
SubscribeStatesRequest msg;
// Empty message: no decode needed
case 20 /* SubscribeStatesRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_subscribe_states_request"), msg);
this->log_receive_message_(LOG_STR("on_subscribe_states_request"));
#endif
this->on_subscribe_states_request(msg);
this->on_subscribe_states_request();
break;
}
case SubscribeLogsRequest::MESSAGE_TYPE: {
@@ -145,13 +151,11 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: {
SubscribeHomeassistantServicesRequest msg;
// Empty message: no decode needed
case 34 /* SubscribeHomeassistantServicesRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"), msg);
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"));
#endif
this->on_subscribe_homeassistant_services_request(msg);
this->on_subscribe_homeassistant_services_request();
break;
}
#endif
@@ -165,13 +169,11 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#ifdef USE_API_HOMEASSISTANT_STATES
case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: {
SubscribeHomeAssistantStatesRequest msg;
// Empty message: no decode needed
case 38 /* SubscribeHomeAssistantStatesRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"), msg);
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"));
#endif
this->on_subscribe_home_assistant_states_request(msg);
this->on_subscribe_home_assistant_states_request();
break;
}
#endif
@@ -374,24 +376,20 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
#endif
#ifdef USE_BLUETOOTH_PROXY
case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: {
SubscribeBluetoothConnectionsFreeRequest msg;
// Empty message: no decode needed
case 80 /* SubscribeBluetoothConnectionsFreeRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"), msg);
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"));
#endif
this->on_subscribe_bluetooth_connections_free_request(msg);
this->on_subscribe_bluetooth_connections_free_request();
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
UnsubscribeBluetoothLEAdvertisementsRequest msg;
// Empty message: no decode needed
case 87 /* UnsubscribeBluetoothLEAdvertisementsRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"), msg);
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"));
#endif
this->on_unsubscribe_bluetooth_le_advertisements_request(msg);
this->on_unsubscribe_bluetooth_le_advertisements_request();
break;
}
#endif
@@ -642,226 +640,4 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
}
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
if (!this->send_hello_response(msg)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
if (!this->send_disconnect_response(msg)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_ping_request(const PingRequest &msg) {
if (!this->send_ping_response(msg)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
if (!this->send_device_info_response(msg)) {
this->on_fatal_error();
}
}
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { this->list_entities(msg); }
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
this->subscribe_states(msg);
}
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); }
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServerConnection::on_subscribe_homeassistant_services_request(
const SubscribeHomeassistantServicesRequest &msg) {
this->subscribe_homeassistant_services(msg);
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
this->subscribe_home_assistant_states(msg);
}
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); }
#endif
#ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (!this->send_noise_encryption_set_key_response(msg)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); }
#endif
#ifdef USE_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); }
#endif
#ifdef USE_CLIMATE
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); }
#endif
#ifdef USE_COVER
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); }
#endif
#ifdef USE_DATETIME_DATE
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); }
#endif
#ifdef USE_DATETIME_DATETIME
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
this->datetime_command(msg);
}
#endif
#ifdef USE_FAN
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); }
#endif
#ifdef USE_LIGHT
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); }
#endif
#ifdef USE_LOCK
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); }
#endif
#ifdef USE_MEDIA_PLAYER
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
this->media_player_command(msg);
}
#endif
#ifdef USE_NUMBER
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); }
#endif
#ifdef USE_SELECT
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); }
#endif
#ifdef USE_SIREN
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); }
#endif
#ifdef USE_SWITCH
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); }
#endif
#ifdef USE_TEXT
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); }
#endif
#ifdef USE_DATETIME_TIME
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); }
#endif
#ifdef USE_UPDATE
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); }
#endif
#ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
this->subscribe_bluetooth_le_advertisements(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
this->bluetooth_device_request(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
this->bluetooth_gatt_get_services(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
this->bluetooth_gatt_read(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
this->bluetooth_gatt_write(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
this->bluetooth_gatt_read_descriptor(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
this->bluetooth_gatt_write_descriptor(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
this->bluetooth_gatt_notify(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
const SubscribeBluetoothConnectionsFreeRequest &msg) {
if (!this->send_subscribe_bluetooth_connections_free_response(msg)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
this->unsubscribe_bluetooth_le_advertisements(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
this->bluetooth_scanner_set_mode(msg);
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
this->subscribe_voice_assistant(msg);
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (!this->send_voice_assistant_get_configuration_response(msg)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
this->voice_assistant_set_configuration(msg);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
this->alarm_control_panel_command(msg);
}
#endif
#ifdef USE_ZWAVE_PROXY
void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); }
#endif
#ifdef USE_ZWAVE_PROXY
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
#endif
#ifdef USE_IR_RF
void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
this->infrared_rf_transmit_raw_timings(msg);
}
#endif
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements for messages
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required
case DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
break; // Skip all checks for these messages
case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only
if (!this->check_connection_setup_()) {
return; // Connection not setup
}
break;
default:
// All other messages require authentication (which includes connection check)
if (!this->check_authenticated_()) {
return; // Authentication failed
}
break;
}
// Call base implementation to process the message
APIServerConnectionBase::read_message(msg_size, msg_type, msg_data);
}
} // namespace esphome::api

View File

@@ -14,6 +14,7 @@ class APIServerConnectionBase : public ProtoService {
protected:
void log_send_message_(const char *name, const char *dump);
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
void log_receive_message_(const LogString *name);
public:
#endif
@@ -23,20 +24,20 @@ class APIServerConnectionBase : public ProtoService {
DumpBuffer dump_buf;
this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
#endif
return this->send_message_(msg, message_type);
return this->send_message_impl(msg, message_type);
}
virtual void on_hello_request(const HelloRequest &value){};
virtual void on_disconnect_request(const DisconnectRequest &value){};
virtual void on_disconnect_response(const DisconnectResponse &value){};
virtual void on_ping_request(const PingRequest &value){};
virtual void on_ping_response(const PingResponse &value){};
virtual void on_device_info_request(const DeviceInfoRequest &value){};
virtual void on_disconnect_request(){};
virtual void on_disconnect_response(){};
virtual void on_ping_request(){};
virtual void on_ping_response(){};
virtual void on_device_info_request(){};
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
virtual void on_list_entities_request(){};
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
virtual void on_subscribe_states_request(){};
#ifdef USE_COVER
virtual void on_cover_command_request(const CoverCommandRequest &value){};
@@ -61,14 +62,14 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
virtual void on_subscribe_homeassistant_services_request(){};
#endif
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
virtual void on_subscribe_home_assistant_states_request(){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
@@ -147,12 +148,11 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
virtual void on_subscribe_bluetooth_connections_free_request(){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &value){};
virtual void on_unsubscribe_bluetooth_le_advertisements_request(){};
#endif
#ifdef USE_BLUETOOTH_PROXY
@@ -228,266 +228,4 @@ class APIServerConnectionBase : public ProtoService {
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
};
class APIServerConnection : public APIServerConnectionBase {
public:
virtual bool send_hello_response(const HelloRequest &msg) = 0;
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
virtual bool send_ping_response(const PingRequest &msg) = 0;
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
#ifdef USE_API_NOISE
virtual bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_COVER
virtual void cover_command(const CoverCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif
#ifdef USE_FAN
virtual void fan_command(const FanCommandRequest &msg) = 0;
#endif
#ifdef USE_LIGHT
virtual void light_command(const LightCommandRequest &msg) = 0;
#endif
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#endif
#ifdef USE_NUMBER
virtual void number_command(const NumberCommandRequest &msg) = 0;
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_SIREN
virtual void siren_command(const SirenCommandRequest &msg) = 0;
#endif
#ifdef USE_SWITCH
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
#endif
#ifdef USE_TEXT
virtual void text_command(const TextCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_TIME
virtual void time_command(const TimeCommandRequest &msg) = 0;
#endif
#ifdef USE_UPDATE
virtual void update_command(const UpdateCommandRequest &msg) = 0;
#endif
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual bool send_subscribe_bluetooth_connections_free_response(
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif
#ifdef USE_ZWAVE_PROXY
virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0;
#endif
#ifdef USE_ZWAVE_PROXY
virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
#endif
#ifdef USE_IR_RF
virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
void on_disconnect_request(const DisconnectRequest &msg) override;
void on_ping_request(const PingRequest &msg) override;
void on_device_info_request(const DeviceInfoRequest &msg) override;
void on_list_entities_request(const ListEntitiesRequest &msg) override;
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
#ifdef USE_API_HOMEASSISTANT_SERVICES
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_COVER
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_FAN
void on_fan_command_request(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
void on_light_command_request(const LightCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_SIREN
void on_siren_command_request(const SirenCommandRequest &msg) override;
#endif
#ifdef USE_SWITCH
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
void on_text_command_request(const TextCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_TIME
void on_time_command_request(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_UPDATE
void on_update_command_request(const UpdateCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif
#ifdef USE_ZWAVE_PROXY
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
#endif
#ifdef USE_ZWAVE_PROXY
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
#endif
#ifdef USE_IR_RF
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
#endif
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
};
} // namespace esphome::api

View File

@@ -211,7 +211,7 @@ void APIServer::loop() {
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
// Fire trigger after client is removed so api.connected reflects the true state
this->client_disconnected_trigger_->trigger(client_name, client_peername);
this->client_disconnected_trigger_.trigger(client_name, client_peername);
#endif
// Don't increment client_index since we need to process the swapped element
}

View File

@@ -227,12 +227,10 @@ class APIServer : public Component,
#endif
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
Trigger<std::string, std::string> *get_client_connected_trigger() { return &this->client_connected_trigger_; }
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
return this->client_disconnected_trigger_;
}
Trigger<std::string, std::string> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; }
#endif
protected:
@@ -253,10 +251,10 @@ class APIServer : public Component,
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
Trigger<std::string, std::string> client_connected_trigger_;
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
Trigger<std::string, std::string> client_disconnected_trigger_;
#endif
// 4-byte aligned types

View File

@@ -25,7 +25,9 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
private:
// Helper to convert value to string - handles the case where value is already a string
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
template<typename T> static std::string value_to_string(T &&val) {
return to_string(std::forward<T>(val)); // NOLINT
}
// Overloads for string types - needed because std::to_string doesn't support them
static std::string value_to_string(char *val) {
@@ -136,12 +138,10 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void set_wants_response() { this->flags_.wants_response = true; }
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
return this->success_trigger_with_response_;
}
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() { return &this->success_trigger_with_response_; }
#endif
Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; }
Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; }
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
void play(const Ts &...x) override {
@@ -187,14 +187,14 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
if (response.is_success()) {
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
if (this->flags_.wants_response) {
this->success_trigger_with_response_->trigger(response.get_json(), args...);
this->success_trigger_with_response_.trigger(response.get_json(), args...);
} else
#endif
{
this->success_trigger_->trigger(args...);
this->success_trigger_.trigger(args...);
}
} else {
this->error_trigger_->trigger(response.get_error_message(), args...);
this->error_trigger_.trigger(response.get_error_message(), args...);
}
},
captured_args);
@@ -251,10 +251,10 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
TemplatableStringValue<Ts...> response_template_{""};
Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
Trigger<JsonObjectConst, Ts...> success_trigger_with_response_;
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
Trigger<Ts...> success_trigger_;
Trigger<std::string, Ts...> error_trigger_;
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
struct Flags {

View File

@@ -112,8 +112,12 @@ class ProtoVarInt {
uint64_t result = buffer[0] & 0x7F;
uint8_t bitpos = 7;
// A 64-bit varint is at most 10 bytes (ceil(64/7)). Reject overlong encodings
// to avoid undefined behavior from shifting uint64_t by >= 64 bits.
uint32_t max_len = std::min(len, uint32_t(10));
// Start from the second byte since we've already processed the first
for (uint32_t i = 1; i < len; i++) {
for (uint32_t i = 1; i < max_len; i++) {
uint8_t val = buffer[i];
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
bitpos += 7;
@@ -402,6 +406,20 @@ class DumpBuffer {
const char *c_str() const { return buf_; }
size_t size() const { return pos_; }
/// Get writable buffer pointer for use with buf_append_printf
char *data() { return buf_; }
/// Get current position for use with buf_append_printf
size_t pos() const { return pos_; }
/// Update position after buf_append_printf call
void set_pos(size_t pos) {
if (pos >= CAPACITY) {
pos_ = CAPACITY - 1;
} else {
pos_ = pos;
}
buf_[pos_] = '\0';
}
private:
void append_impl_(const char *str, size_t len) {
size_t space = CAPACITY - 1 - pos_;
@@ -943,32 +961,16 @@ class ProtoService {
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
virtual void on_no_setup_connection() = 0;
/**
* Create a buffer with a reserved size.
* @param reserve_size The number of bytes to pre-allocate in the buffer. This is a hint
* to optimize memory usage and avoid reallocations during encoding.
* Implementations should aim to allocate at least this size.
* @return A ProtoWriteBuffer object with the reserved size.
*/
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
// Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
ProtoSize size;
msg.calculate_size(size);
uint32_t msg_size = size.get_size();
// Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size);
// Encode message into the buffer
msg.encode(buffer);
// Send the buffer
return this->send_buffer(buffer, message_type);
}
/**
* Send a protobuf message by calculating its size, allocating a buffer, encoding, and sending.
* This is the implementation method - callers should use send_message() which adds logging.
* @param msg The protobuf message to send.
* @param message_type The message type identifier.
* @return True if the message was sent successfully, false otherwise.
*/
virtual bool send_message_impl(const ProtoMessage &msg, uint8_t message_type) = 0;
// Authentication helper methods
inline bool check_connection_setup_() {

View File

@@ -1,5 +1,6 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <limits>
#include "abstract_aqi_calculator.h"
@@ -14,7 +15,11 @@ class AQICalculator : public AbstractAQICalculator {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
float aqi = std::max(pm2_5_index, pm10_0_index);
if (aqi < 0.0f) {
aqi = 0.0f;
}
return static_cast<uint16_t>(std::lround(aqi));
}
protected:
@@ -22,13 +27,27 @@ class AQICalculator : public AbstractAQICalculator {
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f},
{35.5f, 55.4f}, {55.5f, 125.4f},
{125.5f, 225.4f}, {225.5f, std::numeric_limits<float>::max()}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
// clang-format off
{0.0f, 9.1f},
{9.1f, 35.5f},
{35.5f, 55.5f},
{55.5f, 125.5f},
{125.5f, 225.5f},
{225.5f, std::numeric_limits<float>::max()}
// clang-format on
};
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f},
{155.0f, 254.0f}, {255.0f, 354.0f},
{355.0f, 424.0f}, {425.0f, std::numeric_limits<float>::max()}};
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
// clang-format off
{0.0f, 55.0f},
{55.0f, 155.0f},
{155.0f, 255.0f},
{255.0f, 355.0f},
{355.0f, 425.0f},
{425.0f, std::numeric_limits<float>::max()}
// clang-format on
};
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
@@ -45,7 +64,10 @@ class AQICalculator : public AbstractAQICalculator {
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
if (in_range) {
return i;
}
}

View File

@@ -10,7 +10,6 @@ class AQISensor : public sensor::Sensor, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }

View File

@@ -1,5 +1,6 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <limits>
#include "abstract_aqi_calculator.h"
@@ -12,7 +13,11 @@ class CAQICalculator : public AbstractAQICalculator {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
float aqi = std::max(pm2_5_index, pm10_0_index);
if (aqi < 0.0f) {
aqi = 0.0f;
}
return static_cast<uint16_t>(std::lround(aqi));
}
protected:
@@ -21,10 +26,24 @@ class CAQICalculator : public AbstractAQICalculator {
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
{0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits<float>::max()}};
// clang-format off
{0.0f, 15.1f},
{15.1f, 30.1f},
{30.1f, 55.1f},
{55.1f, 110.1f},
{110.1f, std::numeric_limits<float>::max()}
// clang-format on
};
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
{0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits<float>::max()}};
// clang-format off
{0.0f, 25.1f},
{25.1f, 50.1f},
{50.1f, 90.1f},
{90.1f, 180.1f},
{180.1f, std::numeric_limits<float>::max()}
// clang-format on
};
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
@@ -42,7 +61,10 @@ class CAQICalculator : public AbstractAQICalculator {
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
if (in_range) {
return i;
}
}

View File

@@ -41,8 +41,6 @@ void AS3935Component::dump_config() {
#endif
}
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
void AS3935Component::loop() {
if (!this->irq_pin_->digital_read())
return;

View File

@@ -74,7 +74,6 @@ class AS3935Component : public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; }

View File

@@ -22,8 +22,6 @@ static const uint8_t REGISTER_STATUS = 0x0B; // 8 bytes / R
static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
float AS5600Sensor::get_setup_priority() const { return setup_priority::DATA; }
void AS5600Sensor::dump_config() {
LOG_SENSOR("", "AS5600 Sensor", this);
ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_);

View File

@@ -14,7 +14,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
public:
void update() override;
void dump_config() override;
float get_setup_priority() const override;
void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; }
void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; }

View File

@@ -58,8 +58,6 @@ void AS7341Component::dump_config() {
LOG_SENSOR(" ", "NIR", this->nir_);
}
float AS7341Component::get_setup_priority() const { return setup_priority::DATA; }
void AS7341Component::update() {
this->read_channels(this->channel_readings_);

View File

@@ -78,7 +78,6 @@ class AS7341Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_f1_sensor(sensor::Sensor *f1_sensor) { this->f1_ = f1_sensor; }

View File

@@ -146,7 +146,6 @@ void ATM90E26Component::dump_config() {
LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
}
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
uint8_t data[2];

View File

@@ -13,7 +13,6 @@ class ATM90E26Component : public PollingComponent,
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }

View File

@@ -6,8 +6,7 @@ namespace bang_bang {
static const char *const TAG = "bang_bang.climate";
BangBangClimate::BangBangClimate()
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
BangBangClimate::BangBangClimate() = default;
void BangBangClimate::setup() {
this->sensor_->add_on_state_callback([this](float state) {
@@ -160,13 +159,13 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
switch (action) {
case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE:
trig = this->idle_trigger_;
trig = &this->idle_trigger_;
break;
case climate::CLIMATE_ACTION_COOLING:
trig = this->cool_trigger_;
trig = &this->cool_trigger_;
break;
case climate::CLIMATE_ACTION_HEATING:
trig = this->heat_trigger_;
trig = &this->heat_trigger_;
break;
default:
trig = nullptr;
@@ -204,9 +203,9 @@ void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &awa
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
Trigger<> *BangBangClimate::get_idle_trigger() { return &this->idle_trigger_; }
Trigger<> *BangBangClimate::get_cool_trigger() { return &this->cool_trigger_; }
Trigger<> *BangBangClimate::get_heat_trigger() { return &this->heat_trigger_; }
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }

View File

@@ -30,9 +30,9 @@ class BangBangClimate : public climate::Climate, public Component {
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
void set_away_config(const BangBangClimateTargetTempConfig &away_config);
Trigger<> *get_idle_trigger() const;
Trigger<> *get_cool_trigger() const;
Trigger<> *get_heat_trigger() const;
Trigger<> *get_idle_trigger();
Trigger<> *get_cool_trigger();
Trigger<> *get_heat_trigger();
protected:
/// Override control to change settings of the climate device.
@@ -57,17 +57,13 @@ class BangBangClimate : public climate::Climate, public Component {
*
* In idle mode, the controller is assumed to have both heating and cooling disabled.
*/
Trigger<> *idle_trigger_{nullptr};
Trigger<> idle_trigger_;
/** The trigger to call when the controller should switch to cooling mode.
*/
Trigger<> *cool_trigger_{nullptr};
Trigger<> cool_trigger_;
/** The trigger to call when the controller should switch to heating mode.
*
* A null value for this attribute means that the controller has no heating action
* For example window blinds, where only cooling (blinds closed) and not-cooling
* (blinds open) is possible.
*/
Trigger<> *heat_trigger_{nullptr};
Trigger<> heat_trigger_;
/** A reference to the trigger that was previously active.
*
* This is so that the previous trigger can be stopped before enabling a new one.

View File

@@ -265,6 +265,4 @@ void BH1750Sensor::fail_and_reset_() {
this->state_ = IDLE;
}
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace esphome::bh1750

View File

@@ -21,7 +21,6 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
protected:
// State machine states

View File

@@ -5,6 +5,14 @@ namespace esphome::binary_sensor {
static const char *const TAG = "binary_sensor.automation";
// MultiClickTrigger timeout IDs.
// MultiClickTrigger is its own Component instance, so the scheduler scopes
// IDs by component pointer — no risk of collisions between instances.
constexpr uint32_t MULTICLICK_TRIGGER_ID = 0;
constexpr uint32_t MULTICLICK_COOLDOWN_ID = 1;
constexpr uint32_t MULTICLICK_IS_VALID_ID = 2;
constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID = 3;
void MultiClickTrigger::on_state_(bool state) {
// Handle duplicate events
if (state == this->last_state_) {
@@ -27,7 +35,7 @@ void MultiClickTrigger::on_state_(bool state) {
evt.min_length, evt.max_length);
this->at_index_ = 1;
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
} else {
this->schedule_is_valid_(evt.min_length);
this->schedule_is_not_valid_(evt.max_length);
@@ -57,13 +65,13 @@ void MultiClickTrigger::on_state_(bool state) {
this->schedule_is_not_valid_(evt.max_length);
} else if (*this->at_index_ + 1 != this->timing_.size()) {
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
this->cancel_timeout("is_not_valid");
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
this->schedule_is_valid_(evt.min_length);
} else {
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
this->is_valid_ = false;
this->cancel_timeout("is_not_valid");
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
}
*this->at_index_ = *this->at_index_ + 1;
@@ -71,14 +79,14 @@ void MultiClickTrigger::on_state_(bool state) {
void MultiClickTrigger::schedule_cooldown_() {
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
this->is_in_cooldown_ = true;
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
this->is_in_cooldown_ = false;
});
this->at_index_.reset();
this->cancel_timeout("trigger");
this->cancel_timeout("is_valid");
this->cancel_timeout("is_not_valid");
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
}
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
if (min_length == 0) {
@@ -86,13 +94,13 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
return;
}
this->is_valid_ = false;
this->set_timeout("is_valid", min_length, [this]() {
this->set_timeout(MULTICLICK_IS_VALID_ID, min_length, [this]() {
ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
this->is_valid_ = true;
});
}
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
this->set_timeout("is_not_valid", max_length, [this]() {
this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
this->is_valid_ = false;
this->schedule_cooldown_();
@@ -106,9 +114,9 @@ void MultiClickTrigger::cancel() {
void MultiClickTrigger::trigger_() {
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
this->at_index_.reset();
this->cancel_timeout("trigger");
this->cancel_timeout("is_valid");
this->cancel_timeout("is_not_valid");
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
this->trigger();
}

View File

@@ -6,6 +6,14 @@ namespace esphome::binary_sensor {
static const char *const TAG = "sensor.filter";
// Timeout IDs for filter classes.
// Each filter is its own Component instance, so the scheduler scopes
// IDs by component pointer — no risk of collisions between instances.
constexpr uint32_t FILTER_TIMEOUT_ID = 0;
// AutorepeatFilter needs two distinct IDs (both timeouts on the same component)
constexpr uint32_t AUTOREPEAT_TIMING_ID = 0;
constexpr uint32_t AUTOREPEAT_ON_OFF_ID = 1;
void Filter::output(bool value) {
if (this->next_ == nullptr) {
this->parent_->send_state_internal(value);
@@ -23,16 +31,16 @@ void Filter::input(bool value) {
}
void TimeoutFilter::input(bool value) {
this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
this->set_timeout(FILTER_TIMEOUT_ID, this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
// we do not de-dup here otherwise changes from invalid to valid state will not be output
this->output(value);
}
optional<bool> DelayedOnOffFilter::new_value(bool value) {
if (value) {
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
this->set_timeout(FILTER_TIMEOUT_ID, this->on_delay_.value(), [this]() { this->output(true); });
} else {
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
this->set_timeout(FILTER_TIMEOUT_ID, this->off_delay_.value(), [this]() { this->output(false); });
}
return {};
}
@@ -41,10 +49,10 @@ float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HA
optional<bool> DelayedOnFilter::new_value(bool value) {
if (value) {
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->output(true); });
return {};
} else {
this->cancel_timeout("ON");
this->cancel_timeout(FILTER_TIMEOUT_ID);
return false;
}
}
@@ -53,10 +61,10 @@ float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDW
optional<bool> DelayedOffFilter::new_value(bool value) {
if (!value) {
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->output(false); });
return {};
} else {
this->cancel_timeout("OFF");
this->cancel_timeout(FILTER_TIMEOUT_ID);
return true;
}
}
@@ -76,8 +84,8 @@ optional<bool> AutorepeatFilter::new_value(bool value) {
this->next_timing_();
return true;
} else {
this->cancel_timeout("TIMING");
this->cancel_timeout("ON_OFF");
this->cancel_timeout(AUTOREPEAT_TIMING_ID);
this->cancel_timeout(AUTOREPEAT_ON_OFF_ID);
this->active_timing_ = 0;
return false;
}
@@ -88,8 +96,10 @@ void AutorepeatFilter::next_timing_() {
// 1st time: starts waiting the first delay
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
// last time: no delay to start but have to bump the index to reflect the last
if (this->active_timing_ < this->timings_.size())
this->set_timeout("TIMING", this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); });
if (this->active_timing_ < this->timings_.size()) {
this->set_timeout(AUTOREPEAT_TIMING_ID, this->timings_[this->active_timing_].delay,
[this]() { this->next_timing_(); });
}
if (this->active_timing_ <= this->timings_.size()) {
this->active_timing_++;
@@ -104,7 +114,8 @@ void AutorepeatFilter::next_timing_() {
void AutorepeatFilter::next_value_(bool val) {
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
this->output(val); // This is at least the second one so not initial
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
this->set_timeout(AUTOREPEAT_ON_OFF_ID, val ? timing.time_on : timing.time_off,
[this, val]() { this->next_value_(!val); });
}
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -115,7 +126,7 @@ optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
optional<bool> SettleFilter::new_value(bool value) {
if (!this->steady_) {
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this, value]() {
this->steady_ = true;
this->output(value);
});
@@ -123,7 +134,7 @@ optional<bool> SettleFilter::new_value(bool value) {
} else {
this->steady_ = false;
this->output(value);
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->steady_ = true; });
return value;
}
}

View File

@@ -46,16 +46,16 @@ static const uint32_t PKT_TIMEOUT_MS = 200;
void BL0942::loop() {
DataPacket buffer;
int avail = this->available();
size_t avail = this->available();
if (!avail) {
return;
}
if (static_cast<size_t>(avail) < sizeof(buffer)) {
if (avail < sizeof(buffer)) {
if (!this->rx_start_) {
this->rx_start_ = millis();
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%zu bytes)", avail);
this->read_array((uint8_t *) &buffer, avail);
this->rx_start_ = 0;
}

View File

@@ -199,7 +199,6 @@ void BME280Component::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
}
float BME280Component::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }

View File

@@ -76,7 +76,6 @@ class BME280Component : public PollingComponent {
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -233,8 +233,6 @@ void BME680Component::dump_config() {
}
}
float BME680Component::get_setup_priority() const { return setup_priority::DATA; }
void BME680Component::update() {
uint8_t meas_control = 0; // No need to fetch, we're setting all fields
meas_control |= (this->temperature_oversampling_ & 0b111) << 5;

View File

@@ -99,7 +99,6 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice {
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -89,8 +89,9 @@ async def to_code(config):
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
# Although this component does not use SPI, the BSEC library requires the SPI library
# Although this component does not use SPI/Wire directly, the BSEC library requires them
cg.add_library("SPI", None)
cg.add_library("Wire", None)
cg.add_define("USE_BSEC")
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")

View File

@@ -181,8 +181,6 @@ void BME680BSECComponent::dump_config() {
LOG_SENSOR(" ", "Breath VOC Equivalent", this->breath_voc_equivalent_sensor_);
}
float BME680BSECComponent::get_setup_priority() const { return setup_priority::DATA; }
void BME680BSECComponent::loop() {
this->run_();

View File

@@ -64,7 +64,6 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
protected:

View File

@@ -106,8 +106,6 @@ void BME68xBSEC2Component::dump_config() {
#endif
}
float BME68xBSEC2Component::get_setup_priority() const { return setup_priority::DATA; }
void BME68xBSEC2Component::loop() {
this->run_();

View File

@@ -48,7 +48,6 @@ class BME68xBSEC2Component : public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void set_algorithm_output(AlgorithmOutput algorithm_output) { this->algorithm_output_ = algorithm_output; }

View File

@@ -263,7 +263,6 @@ void BMI160Component::update() {
this->status_clear_warning();
}
float BMI160Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bmi160
} // namespace esphome

View File

@@ -14,8 +14,6 @@ class BMI160Component : public PollingComponent, public i2c::I2CDevice {
void update() override;
float get_setup_priority() const override;
void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; }
void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; }
void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; }

View File

@@ -131,7 +131,6 @@ bool BMP085Component::set_mode_(uint8_t mode) {
ESP_LOGV(TAG, "Setting mode to 0x%02X", mode);
return this->write_byte(BMP085_REGISTER_CONTROL, mode);
}
float BMP085Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bmp085
} // namespace esphome

View File

@@ -18,8 +18,6 @@ class BMP085Component : public PollingComponent, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
struct CalibrationData {
int16_t ac1, ac2, ac3;

View File

@@ -148,7 +148,6 @@ void BMP280Component::dump_config() {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
}
float BMP280Component::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }

View File

@@ -64,7 +64,6 @@ class BMP280Component : public PollingComponent {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -179,7 +179,6 @@ void BMP3XXComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
}
}
float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); }

View File

@@ -73,7 +73,6 @@ class BMP3XXComponent : public PollingComponent {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -156,7 +156,7 @@ void CC1101Component::call_listeners_(const std::vector<uint8_t> &packet, float
for (auto &listener : this->listeners_) {
listener->on_packet(packet, freq_offset, rssi, lqi);
}
this->packet_trigger_->trigger(packet, freq_offset, rssi, lqi);
this->packet_trigger_.trigger(packet, freq_offset, rssi, lqi);
}
void CC1101Component::loop() {

View File

@@ -79,7 +79,7 @@ class CC1101Component : public Component,
// Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; }
protected:
uint16_t chip_id_{0};
@@ -96,8 +96,7 @@ class CC1101Component : public Component,
// Packet handling
void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi);
Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
Trigger<std::vector<uint8_t>, float, float, uint8_t> packet_trigger_;
std::vector<uint8_t> packet_;
std::vector<CC1101Listener *> listeners_;

View File

@@ -7,8 +7,6 @@ namespace cd74hc4067 {
static const char *const TAG = "cd74hc4067";
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
void CD74HC4067Component::setup() {
this->pin_s0_->setup();
this->pin_s1_->setup();

View File

@@ -13,7 +13,6 @@ class CD74HC4067Component : public Component {
/// Set up the internal sensor array.
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
/// setting pin active by setting the right combination of the four multiplexer input pins
void activate_pin(uint8_t pin);

View File

@@ -1,109 +1,44 @@
#include "climate_mode.h"
#include "esphome/core/progmem.h"
namespace esphome::climate {
// Climate mode strings indexed by ClimateMode enum (0-6): OFF, HEAT_COOL, COOL, HEAT, FAN_ONLY, DRY, AUTO
PROGMEM_STRING_TABLE(ClimateModeStrings, "OFF", "HEAT_COOL", "COOL", "HEAT", "FAN_ONLY", "DRY", "AUTO", "UNKNOWN");
const LogString *climate_mode_to_string(ClimateMode mode) {
switch (mode) {
case CLIMATE_MODE_OFF:
return LOG_STR("OFF");
case CLIMATE_MODE_HEAT_COOL:
return LOG_STR("HEAT_COOL");
case CLIMATE_MODE_AUTO:
return LOG_STR("AUTO");
case CLIMATE_MODE_COOL:
return LOG_STR("COOL");
case CLIMATE_MODE_HEAT:
return LOG_STR("HEAT");
case CLIMATE_MODE_FAN_ONLY:
return LOG_STR("FAN_ONLY");
case CLIMATE_MODE_DRY:
return LOG_STR("DRY");
default:
return LOG_STR("UNKNOWN");
}
return ClimateModeStrings::get_log_str(static_cast<uint8_t>(mode), ClimateModeStrings::LAST_INDEX);
}
// Climate action strings indexed by ClimateAction enum (0,2-6): OFF, (gap), COOLING, HEATING, IDLE, DRYING, FAN
PROGMEM_STRING_TABLE(ClimateActionStrings, "OFF", "UNKNOWN", "COOLING", "HEATING", "IDLE", "DRYING", "FAN", "UNKNOWN");
const LogString *climate_action_to_string(ClimateAction action) {
switch (action) {
case CLIMATE_ACTION_OFF:
return LOG_STR("OFF");
case CLIMATE_ACTION_COOLING:
return LOG_STR("COOLING");
case CLIMATE_ACTION_HEATING:
return LOG_STR("HEATING");
case CLIMATE_ACTION_IDLE:
return LOG_STR("IDLE");
case CLIMATE_ACTION_DRYING:
return LOG_STR("DRYING");
case CLIMATE_ACTION_FAN:
return LOG_STR("FAN");
default:
return LOG_STR("UNKNOWN");
}
return ClimateActionStrings::get_log_str(static_cast<uint8_t>(action), ClimateActionStrings::LAST_INDEX);
}
// Climate fan mode strings indexed by ClimateFanMode enum (0-9): ON, OFF, AUTO, LOW, MEDIUM, HIGH, MIDDLE, FOCUS,
// DIFFUSE, QUIET
PROGMEM_STRING_TABLE(ClimateFanModeStrings, "ON", "OFF", "AUTO", "LOW", "MEDIUM", "HIGH", "MIDDLE", "FOCUS", "DIFFUSE",
"QUIET", "UNKNOWN");
const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
switch (fan_mode) {
case climate::CLIMATE_FAN_ON:
return LOG_STR("ON");
case climate::CLIMATE_FAN_OFF:
return LOG_STR("OFF");
case climate::CLIMATE_FAN_AUTO:
return LOG_STR("AUTO");
case climate::CLIMATE_FAN_LOW:
return LOG_STR("LOW");
case climate::CLIMATE_FAN_MEDIUM:
return LOG_STR("MEDIUM");
case climate::CLIMATE_FAN_HIGH:
return LOG_STR("HIGH");
case climate::CLIMATE_FAN_MIDDLE:
return LOG_STR("MIDDLE");
case climate::CLIMATE_FAN_FOCUS:
return LOG_STR("FOCUS");
case climate::CLIMATE_FAN_DIFFUSE:
return LOG_STR("DIFFUSE");
case climate::CLIMATE_FAN_QUIET:
return LOG_STR("QUIET");
default:
return LOG_STR("UNKNOWN");
}
return ClimateFanModeStrings::get_log_str(static_cast<uint8_t>(fan_mode), ClimateFanModeStrings::LAST_INDEX);
}
// Climate swing mode strings indexed by ClimateSwingMode enum (0-3): OFF, BOTH, VERTICAL, HORIZONTAL
PROGMEM_STRING_TABLE(ClimateSwingModeStrings, "OFF", "BOTH", "VERTICAL", "HORIZONTAL", "UNKNOWN");
const LogString *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
return LOG_STR("OFF");
case climate::CLIMATE_SWING_BOTH:
return LOG_STR("BOTH");
case climate::CLIMATE_SWING_VERTICAL:
return LOG_STR("VERTICAL");
case climate::CLIMATE_SWING_HORIZONTAL:
return LOG_STR("HORIZONTAL");
default:
return LOG_STR("UNKNOWN");
}
return ClimateSwingModeStrings::get_log_str(static_cast<uint8_t>(swing_mode), ClimateSwingModeStrings::LAST_INDEX);
}
// Climate preset strings indexed by ClimatePreset enum (0-7): NONE, HOME, AWAY, BOOST, COMFORT, ECO, SLEEP, ACTIVITY
PROGMEM_STRING_TABLE(ClimatePresetStrings, "NONE", "HOME", "AWAY", "BOOST", "COMFORT", "ECO", "SLEEP", "ACTIVITY",
"UNKNOWN");
const LogString *climate_preset_to_string(ClimatePreset preset) {
switch (preset) {
case climate::CLIMATE_PRESET_NONE:
return LOG_STR("NONE");
case climate::CLIMATE_PRESET_HOME:
return LOG_STR("HOME");
case climate::CLIMATE_PRESET_ECO:
return LOG_STR("ECO");
case climate::CLIMATE_PRESET_AWAY:
return LOG_STR("AWAY");
case climate::CLIMATE_PRESET_BOOST:
return LOG_STR("BOOST");
case climate::CLIMATE_PRESET_COMFORT:
return LOG_STR("COMFORT");
case climate::CLIMATE_PRESET_SLEEP:
return LOG_STR("SLEEP");
case climate::CLIMATE_PRESET_ACTIVITY:
return LOG_STR("ACTIVITY");
default:
return LOG_STR("UNKNOWN");
}
return ClimatePresetStrings::get_log_str(static_cast<uint8_t>(preset), ClimatePresetStrings::LAST_INDEX);
}
} // namespace esphome::climate

View File

@@ -10,8 +10,6 @@ namespace cm1106 {
class CM1106Component : public PollingComponent, public uart::UARTDevice {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void setup() override;
void update() override;
void dump_config() override;

View File

@@ -10,8 +10,6 @@ namespace combination {
class CombinationComponent : public Component, public sensor::Sensor {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
/// @brief Logs all source sensor's names
virtual void log_source_sensors() = 0;

View File

@@ -17,3 +17,9 @@ CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"
CONF_ROWS = "rows"
CONF_USE_PSRAM = "use_psram"
ICON_CURRENT_DC = "mdi:current-dc"
ICON_SOLAR_PANEL = "mdi:solar-panel"
ICON_SOLAR_POWER = "mdi:solar-power"
UNIT_AMPERE_HOUR = "Ah"

View File

@@ -1,3 +1,5 @@
import logging
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
@@ -9,6 +11,7 @@ from esphome.const import (
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_IDLE,
CONF_ON_OPEN,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
@@ -32,9 +35,10 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.types import ConfigType, TemplateArgsType
IS_PLATFORM_COMPONENT = True
@@ -53,6 +57,8 @@ DEVICE_CLASSES = [
DEVICE_CLASS_WINDOW,
]
_LOGGER = logging.getLogger(__name__)
cover_ns = cg.esphome_ns.namespace("cover")
Cover = cover_ns.class_("Cover", cg.EntityBase)
@@ -83,14 +89,29 @@ ControlAction = cover_ns.class_("ControlAction", automation.Action)
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
# Triggers
CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template())
CoverOpenedTrigger = cover_ns.class_(
"CoverOpenedTrigger", automation.Trigger.template()
)
CoverClosedTrigger = cover_ns.class_(
"CoverClosedTrigger", automation.Trigger.template()
)
CoverTrigger = cover_ns.class_("CoverTrigger", automation.Trigger.template())
# Cover-specific constants
CONF_ON_CLOSED = "on_closed"
CONF_ON_OPENED = "on_opened"
CONF_ON_OPENING = "on_opening"
CONF_ON_CLOSING = "on_closing"
TRIGGERS = {
CONF_ON_OPEN: CoverOpenedTrigger, # Deprecated, use on_opened
CONF_ON_OPENED: CoverOpenedTrigger,
CONF_ON_CLOSED: CoverClosedTrigger,
CONF_ON_CLOSING: CoverTrigger.template(CoverOperation.COVER_OPERATION_CLOSING),
CONF_ON_OPENING: CoverTrigger.template(CoverOperation.COVER_OPERATION_OPENING),
CONF_ON_IDLE: CoverTrigger.template(CoverOperation.COVER_OPERATION_IDLE),
}
_COVER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
@@ -111,16 +132,14 @@ _COVER_SCHEMA = (
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
**{
cv.Optional(conf): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class),
}
)
for conf, trigger_class in TRIGGERS.items()
},
}
)
)
@@ -157,12 +176,14 @@ async def setup_cover_core_(var, config):
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))
for conf in config.get(CONF_ON_OPEN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLOSED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_ON_OPEN in config:
_LOGGER.warning(
"'on_open' is deprecated, use 'on_opened'. Will be removed in 2026.8.0"
)
for trigger_conf in TRIGGERS:
for conf in config.get(trigger_conf, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
@@ -258,6 +279,26 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return var
COVER_CONDITION_SCHEMA = cv.maybe_simple_value(
{cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID
)
async def cover_condition_to_code(
config: ConfigType, condition_id: ID, template_arg: MockObj, args: TemplateArgsType
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
automation.register_condition(
"cover.is_open", CoverIsOpenCondition, COVER_CONDITION_SCHEMA
)(cover_condition_to_code)
automation.register_condition(
"cover.is_closed", CoverIsClosedCondition, COVER_CONDITION_SCHEMA
)(cover_condition_to_code)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(cover_ns.using)

View File

@@ -90,44 +90,53 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
Cover *cover_;
};
template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
template<bool OPEN, typename... Ts> class CoverPositionCondition : public Condition<Ts...> {
public:
CoverIsOpenCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->is_fully_open(); }
CoverPositionCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->position == (OPEN ? COVER_OPEN : COVER_CLOSED); }
protected:
Cover *cover_;
};
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
template<typename... Ts> using CoverIsOpenCondition = CoverPositionCondition<true, Ts...>;
template<typename... Ts> using CoverIsClosedCondition = CoverPositionCondition<false, Ts...>;
template<bool OPEN> class CoverPositionTrigger : public Trigger<> {
public:
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->is_fully_closed(); }
CoverPositionTrigger(Cover *a_cover) {
a_cover->add_on_state_callback([this, a_cover]() {
if (a_cover->position != this->last_position_) {
this->last_position_ = a_cover->position;
if (a_cover->position == (OPEN ? COVER_OPEN : COVER_CLOSED))
this->trigger();
}
});
}
protected:
Cover *cover_;
float last_position_{NAN};
};
class CoverOpenTrigger : public Trigger<> {
using CoverOpenedTrigger = CoverPositionTrigger<true>;
using CoverClosedTrigger = CoverPositionTrigger<false>;
template<CoverOperation OP> class CoverTrigger : public Trigger<> {
public:
CoverOpenTrigger(Cover *a_cover) {
CoverTrigger(Cover *a_cover) {
a_cover->add_on_state_callback([this, a_cover]() {
if (a_cover->is_fully_open()) {
this->trigger();
auto current_op = a_cover->current_operation;
if (current_op == OP) {
if (!this->last_operation_.has_value() || this->last_operation_.value() != OP) {
this->trigger();
}
}
this->last_operation_ = current_op;
});
}
};
class CoverClosedTrigger : public Trigger<> {
public:
CoverClosedTrigger(Cover *a_cover) {
a_cover->add_on_state_callback([this, a_cover]() {
if (a_cover->is_fully_closed()) {
this->trigger();
}
});
}
protected:
optional<CoverOperation> last_operation_{};
};
} // namespace esphome::cover

View File

@@ -10,9 +10,6 @@ namespace esphome::cover {
static const char *const TAG = "cover";
const float COVER_OPEN = 1.0f;
const float COVER_CLOSED = 0.0f;
const LogString *cover_command_to_str(float pos) {
if (pos == COVER_OPEN) {
return LOG_STR("OPEN");
@@ -22,17 +19,11 @@ const LogString *cover_command_to_str(float pos) {
return LOG_STR("UNKNOWN");
}
}
// Cover operation strings indexed by CoverOperation enum (0-2): IDLE, OPENING, CLOSING, plus UNKNOWN
PROGMEM_STRING_TABLE(CoverOperationStrings, "IDLE", "OPENING", "CLOSING", "UNKNOWN");
const LogString *cover_operation_to_str(CoverOperation op) {
switch (op) {
case COVER_OPERATION_IDLE:
return LOG_STR("IDLE");
case COVER_OPERATION_OPENING:
return LOG_STR("OPENING");
case COVER_OPERATION_CLOSING:
return LOG_STR("CLOSING");
default:
return LOG_STR("UNKNOWN");
}
return CoverOperationStrings::get_log_str(static_cast<uint8_t>(op), CoverOperationStrings::LAST_INDEX);
}
Cover::Cover() : position{COVER_OPEN} {}

View File

@@ -10,8 +10,8 @@
namespace esphome::cover {
const extern float COVER_OPEN;
const extern float COVER_CLOSED;
static constexpr float COVER_OPEN = 1.0f;
static constexpr float COVER_CLOSED = 0.0f;
#define LOG_COVER(prefix, type, obj) \
if ((obj) != nullptr) { \

View File

@@ -62,8 +62,6 @@ void CSE7761Component::dump_config() {
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
}
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
void CSE7761Component::update() {
if (this->data_.ready) {
this->get_data_();

View File

@@ -28,7 +28,6 @@ class CSE7761Component : public PollingComponent, public uart::UARTDevice {
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -7,7 +7,6 @@ namespace esphome {
namespace cse7766 {
static const char *const TAG = "cse7766";
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
void CSE7766Component::loop() {
const uint32_t now = App.get_loop_component_start_time();
@@ -16,28 +15,41 @@ void CSE7766Component::loop() {
this->raw_data_index_ = 0;
}
if (this->available() == 0) {
// Early return prevents updating last_transmission_ when no data is available.
size_t avail = this->available();
if (avail == 0) {
return;
}
this->last_transmission_ = now;
while (this->available() != 0) {
this->read_byte(&this->raw_data_[this->raw_data_index_]);
if (!this->check_byte_()) {
this->raw_data_index_ = 0;
this->status_set_warning();
continue;
}
if (this->raw_data_index_ == 23) {
this->parse_data_();
this->status_clear_warning();
// Read all available bytes in batches to reduce UART call overhead.
// At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
uint8_t buf[CSE7766_RAW_DATA_SIZE];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
this->raw_data_index_ = (this->raw_data_index_ + 1) % 24;
for (size_t i = 0; i < to_read; i++) {
this->raw_data_[this->raw_data_index_] = buf[i];
if (!this->check_byte_()) {
this->raw_data_index_ = 0;
this->status_set_warning();
continue;
}
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
this->parse_data_();
this->status_clear_warning();
}
this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
}
}
}
float CSE7766Component::get_setup_priority() const { return setup_priority::DATA; }
bool CSE7766Component::check_byte_() {
uint8_t index = this->raw_data_index_;
@@ -54,14 +66,15 @@ bool CSE7766Component::check_byte_() {
return true;
}
if (index == 23) {
if (index == CSE7766_RAW_DATA_SIZE - 1) {
uint8_t checksum = 0;
for (uint8_t i = 2; i < 23; i++) {
for (uint8_t i = 2; i < CSE7766_RAW_DATA_SIZE - 1; i++) {
checksum += this->raw_data_[i];
}
if (checksum != this->raw_data_[23]) {
ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]);
if (checksum != this->raw_data_[CSE7766_RAW_DATA_SIZE - 1]) {
ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum,
this->raw_data_[CSE7766_RAW_DATA_SIZE - 1]);
return false;
}
return true;
@@ -152,6 +165,10 @@ void CSE7766Component::parse_data_() {
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(power);
}
} else if (this->power_sensor_ != nullptr) {
// No valid power measurement from chip - publish 0W to avoid stale readings
// This typically happens when current is below the measurable threshold (~50mA)
this->power_sensor_->publish_state(0.0f);
}
float current = 0.0f;

View File

@@ -8,6 +8,8 @@
namespace esphome {
namespace cse7766 {
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
class CSE7766Component : public Component, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
@@ -23,7 +25,6 @@ class CSE7766Component : public Component, public uart::UARTDevice {
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
void loop() override;
float get_setup_priority() const override;
void dump_config() override;
protected:
@@ -34,7 +35,7 @@ class CSE7766Component : public Component, public uart::UARTDevice {
this->raw_data_[start_index + 2]);
}
uint8_t raw_data_[24];
uint8_t raw_data_[CSE7766_RAW_DATA_SIZE];
uint8_t raw_data_index_{0};
uint32_t last_transmission_{0};
sensor::Sensor *voltage_sensor_{nullptr};

View File

@@ -66,7 +66,7 @@ void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
this->direction_idle_();
this->malfunction_trigger_->trigger();
this->malfunction_trigger_.trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
this->name_.c_str());
} else if (this->is_opening_blocked_()) { // Blocked
@@ -87,7 +87,7 @@ void CurrentBasedCover::loop() {
} else if (this->current_operation == COVER_OPERATION_CLOSING) {
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
this->direction_idle_();
this->malfunction_trigger_->trigger();
this->malfunction_trigger_.trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
this->name_.c_str());
} else if (this->is_closing_blocked_()) { // Blocked
@@ -159,7 +159,6 @@ void CurrentBasedCover::dump_config() {
this->start_sensing_delay_ / 1e3f, YESNO(this->malfunction_detection_));
}
float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; }
void CurrentBasedCover::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) {
this->prev_command_trigger_->stop_action();
@@ -221,15 +220,15 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
Trigger<> *trig;
switch (dir) {
case COVER_OPERATION_IDLE:
trig = this->stop_trigger_;
trig = &this->stop_trigger_;
break;
case COVER_OPERATION_OPENING:
this->last_operation_ = dir;
trig = this->open_trigger_;
trig = &this->open_trigger_;
break;
case COVER_OPERATION_CLOSING:
this->last_operation_ = dir;
trig = this->close_trigger_;
trig = &this->close_trigger_;
break;
default:
return;

View File

@@ -14,11 +14,10 @@ class CurrentBasedCover : public cover::Cover, public Component {
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
Trigger<> *get_open_trigger() const { return this->open_trigger_; }
Trigger<> *get_open_trigger() { return &this->open_trigger_; }
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
void set_open_moving_current_threshold(float open_moving_current_threshold) {
this->open_moving_current_threshold_ = open_moving_current_threshold;
@@ -28,7 +27,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
}
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; }
Trigger<> *get_close_trigger() { return &this->close_trigger_; }
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
void set_close_moving_current_threshold(float close_moving_current_threshold) {
this->close_moving_current_threshold_ = close_moving_current_threshold;
@@ -44,7 +43,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; }
Trigger<> *get_malfunction_trigger() { return &this->malfunction_trigger_; }
cover::CoverTraits get_traits() override;
@@ -64,23 +63,23 @@ class CurrentBasedCover : public cover::Cover, public Component {
void recompute_position_();
Trigger<> *stop_trigger_{new Trigger<>()};
Trigger<> stop_trigger_;
sensor::Sensor *open_sensor_{nullptr};
Trigger<> *open_trigger_{new Trigger<>()};
Trigger<> open_trigger_;
float open_moving_current_threshold_;
float open_obstacle_current_threshold_{FLT_MAX};
uint32_t open_duration_;
sensor::Sensor *close_sensor_{nullptr};
Trigger<> *close_trigger_{new Trigger<>()};
Trigger<> close_trigger_;
float close_moving_current_threshold_;
float close_obstacle_current_threshold_{FLT_MAX};
uint32_t close_duration_;
uint32_t max_duration_{UINT32_MAX};
bool malfunction_detection_{true};
Trigger<> *malfunction_trigger_{new Trigger<>()};
Trigger<> malfunction_trigger_;
uint32_t start_sensing_delay_;
float obstacle_rollback_;

View File

@@ -104,8 +104,6 @@ void DalyBmsComponent::loop() {
}
}
float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; }
void DalyBmsComponent::request_data_(uint8_t data_id) {
uint8_t request_message[DALY_FRAME_SIZE];

View File

@@ -72,7 +72,6 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
void update() override;
void loop() override;
float get_setup_priority() const override;
void set_address(uint8_t address) { this->addr_ = address; }
protected:

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components.const import ICON_CURRENT_DC, UNIT_AMPERE_HOUR
import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_LEVEL,
@@ -55,14 +56,11 @@ CONF_CELL_15_VOLTAGE = "cell_15_voltage"
CONF_CELL_16_VOLTAGE = "cell_16_voltage"
CONF_CELL_17_VOLTAGE = "cell_17_voltage"
CONF_CELL_18_VOLTAGE = "cell_18_voltage"
ICON_CURRENT_DC = "mdi:current-dc"
ICON_BATTERY_OUTLINE = "mdi:battery-outline"
ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up"
ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down"
ICON_CAR_BATTERY = "mdi:car-battery"
UNIT_AMPERE_HOUR = "Ah"
TYPES = [
CONF_VOLTAGE,
CONF_CURRENT,

View File

@@ -1,4 +1,5 @@
#include "dfplayer.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -131,140 +132,149 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
}
void DFPlayer::loop() {
// Read message
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
switch (this->read_pos_) {
case 0: // Start mark
if (byte != 0x7E)
continue;
break;
case 1: // Version
if (byte != 0xFF) {
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
break;
case 2: // Buffer length
if (byte != 0x06) {
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
break;
case 9: // End byte
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char byte_sequence[100];
byte_sequence[0] = '\0';
for (size_t i = 0; i < this->read_pos_ + 1; ++i) {
snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ",
this->read_buffer_[i]);
}
ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence);
#endif
if (byte != 0xEF) {
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
// Parse valid received command
uint8_t cmd = this->read_buffer_[3];
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
switch (cmd) {
case 0x3A:
if (argument == 1) {
ESP_LOGI(TAG, "USB loaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card loaded");
}
break;
case 0x3B:
if (argument == 1) {
ESP_LOGI(TAG, "USB unloaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card unloaded");
}
break;
case 0x3F:
if (argument == 1) {
ESP_LOGI(TAG, "USB available");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card available");
} else if (argument == 3) {
ESP_LOGI(TAG, "USB, TF Card available");
}
break;
case 0x40:
ESP_LOGV(TAG, "Nack");
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
switch (argument) {
case 0x01:
ESP_LOGE(TAG, "Module is busy or uninitialized");
break;
case 0x02:
ESP_LOGE(TAG, "Module is in sleep mode");
break;
case 0x03:
ESP_LOGE(TAG, "Serial receive error");
break;
case 0x04:
ESP_LOGE(TAG, "Checksum incorrect");
break;
case 0x05:
ESP_LOGE(TAG, "Specified track is out of current track scope");
this->is_playing_ = false;
break;
case 0x06:
ESP_LOGE(TAG, "Specified track is not found");
this->is_playing_ = false;
break;
case 0x07:
ESP_LOGE(TAG, "Insertion error (an inserting operation only can be done when a track is being played)");
break;
case 0x08:
ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)");
break;
case 0x09:
ESP_LOGE(TAG, "Entered into sleep mode");
this->is_playing_ = false;
break;
}
break;
case 0x41:
ESP_LOGV(TAG, "Ack ok");
this->is_playing_ |= this->ack_set_is_playing_;
this->is_playing_ &= !this->ack_reset_is_playing_;
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
break;
case 0x3C:
ESP_LOGV(TAG, "Playback finished (USB drive)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
case 0x3D:
ESP_LOGV(TAG, "Playback finished (SD card)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
break;
default:
ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument);
}
this->sent_cmd_ = 0;
this->read_pos_ = 0;
continue;
// Read all available bytes in batches to reduce UART call overhead.
size_t avail = this->available();
uint8_t buf[64];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
for (size_t bi = 0; bi < to_read; bi++) {
uint8_t byte = buf[bi];
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
switch (this->read_pos_) {
case 0: // Start mark
if (byte != 0x7E)
continue;
break;
case 1: // Version
if (byte != 0xFF) {
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
break;
case 2: // Buffer length
if (byte != 0x06) {
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
break;
case 9: // End byte
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char byte_sequence[100];
byte_sequence[0] = '\0';
for (size_t i = 0; i < this->read_pos_ + 1; ++i) {
snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ",
this->read_buffer_[i]);
}
ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence);
#endif
if (byte != 0xEF) {
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
// Parse valid received command
uint8_t cmd = this->read_buffer_[3];
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
switch (cmd) {
case 0x3A:
if (argument == 1) {
ESP_LOGI(TAG, "USB loaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card loaded");
}
break;
case 0x3B:
if (argument == 1) {
ESP_LOGI(TAG, "USB unloaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card unloaded");
}
break;
case 0x3F:
if (argument == 1) {
ESP_LOGI(TAG, "USB available");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card available");
} else if (argument == 3) {
ESP_LOGI(TAG, "USB, TF Card available");
}
break;
case 0x40:
ESP_LOGV(TAG, "Nack");
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
switch (argument) {
case 0x01:
ESP_LOGE(TAG, "Module is busy or uninitialized");
break;
case 0x02:
ESP_LOGE(TAG, "Module is in sleep mode");
break;
case 0x03:
ESP_LOGE(TAG, "Serial receive error");
break;
case 0x04:
ESP_LOGE(TAG, "Checksum incorrect");
break;
case 0x05:
ESP_LOGE(TAG, "Specified track is out of current track scope");
this->is_playing_ = false;
break;
case 0x06:
ESP_LOGE(TAG, "Specified track is not found");
this->is_playing_ = false;
break;
case 0x07:
ESP_LOGE(TAG,
"Insertion error (an inserting operation only can be done when a track is being played)");
break;
case 0x08:
ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)");
break;
case 0x09:
ESP_LOGE(TAG, "Entered into sleep mode");
this->is_playing_ = false;
break;
}
break;
case 0x41:
ESP_LOGV(TAG, "Ack ok");
this->is_playing_ |= this->ack_set_is_playing_;
this->is_playing_ &= !this->ack_reset_is_playing_;
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
break;
case 0x3C:
ESP_LOGV(TAG, "Playback finished (USB drive)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
case 0x3D:
ESP_LOGV(TAG, "Playback finished (SD card)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
break;
default:
ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument);
}
this->sent_cmd_ = 0;
this->read_pos_ = 0;
continue;
}
this->read_buffer_[this->read_pos_] = byte;
this->read_pos_++;
}
this->read_buffer_[this->read_pos_] = byte;
this->read_pos_++;
}
}
void DFPlayer::dump_config() {

View File

@@ -63,8 +63,6 @@ void DHT::update() {
}
}
float DHT::get_setup_priority() const { return setup_priority::DATA; }
void DHT::set_dht_model(DHTModel model) {
this->model_ = model;
this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT;

View File

@@ -51,8 +51,6 @@ class DHT : public PollingComponent {
void dump_config() override;
/// Update sensor values and push them to the frontend.
void update() override;
/// HARDWARE_LATE setup priority.
float get_setup_priority() const override;
protected:
bool read_sensor_(float *temperature, float *humidity, bool report_errors);

View File

@@ -49,7 +49,7 @@ void DHT12Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
float DHT12Component::get_setup_priority() const { return setup_priority::DATA; }
bool DHT12Component::read_data_(uint8_t *data) {
if (!this->read_bytes(0, data, 5)) {
ESP_LOGW(TAG, "Updating DHT12 failed!");

View File

@@ -11,7 +11,6 @@ class DHT12Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -0,0 +1,57 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
CODEOWNERS = ["@SimonFischer04"]
DEPENDENCIES = ["uart"]
CONF_DLMS_METER_ID = "dlms_meter_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_PROVIDER = "provider"
PROVIDERS = {"generic": 0, "netznoe": 1}
dlms_meter_component_ns = cg.esphome_ns.namespace("dlms_meter")
DlmsMeterComponent = dlms_meter_component_ns.class_(
"DlmsMeterComponent", cg.Component, uart.UARTDevice
)
def validate_key(value):
value = cv.string_strict(value)
if len(value) != 32:
raise cv.Invalid("Decryption key must be 32 hex characters (16 bytes)")
try:
return [int(value[i : i + 2], 16) for i in range(0, 32, 2)]
except ValueError as exc:
raise cv.Invalid("Decryption key must be hex values from 00 to FF") from exc
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DlmsMeterComponent),
cv.Required(CONF_DECRYPTION_KEY): validate_key,
cv.Optional(CONF_PROVIDER, default="generic"): cv.enum(
PROVIDERS, lower=True
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32]),
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"dlms_meter", baud_rate=2400, require_rx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
key = ", ".join(str(b) for b in config[CONF_DECRYPTION_KEY])
cg.add(var.set_decryption_key(cg.RawExpression(f"{{{key}}}")))
cg.add(var.set_provider(PROVIDERS[config[CONF_PROVIDER]]))

View File

@@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
/*
+-------------------------------+
| Ciphering Service |
+-------------------------------+
| System Title Length |
+-------------------------------+
| |
| |
| |
| System |
| Title |
| |
| |
| |
+-------------------------------+
| Length | (1 or 3 Bytes)
+-------------------------------+
| Security Control Byte |
+-------------------------------+
| |
| Frame |
| Counter |
| |
+-------------------------------+
| |
~ ~
Encrypted Payload
~ ~
| |
+-------------------------------+
Ciphering Service: 0xDB (General-Glo-Ciphering)
System Title Length: 0x08
System Title: Unique ID of meter
Length: 1 Byte=Length <= 127, 3 Bytes=Length > 127 (0x82 & 2 Bytes length)
Security Control Byte:
- Bit 3…0: Security_Suite_Id
- Bit 4: "A" subfield: indicates that authentication is applied
- Bit 5: "E" subfield: indicates that encryption is applied
- Bit 6: Key_Set subfield: 0 = Unicast, 1 = Broadcast
- Bit 7: Indicates the use of compression.
*/
static constexpr uint8_t DLMS_HEADER_LENGTH = 16;
static constexpr uint8_t DLMS_HEADER_EXT_OFFSET = 2; // Extra offset for extended length header
static constexpr uint8_t DLMS_CIPHER_OFFSET = 0;
static constexpr uint8_t DLMS_SYST_OFFSET = 1;
static constexpr uint8_t DLMS_LENGTH_OFFSET = 10;
static constexpr uint8_t TWO_BYTE_LENGTH = 0x82;
static constexpr uint8_t DLMS_LENGTH_CORRECTION = 5; // Header bytes included in length field
static constexpr uint8_t DLMS_SECBYTE_OFFSET = 11;
static constexpr uint8_t DLMS_FRAMECOUNTER_OFFSET = 12;
static constexpr uint8_t DLMS_FRAMECOUNTER_LENGTH = 4;
static constexpr uint8_t DLMS_PAYLOAD_OFFSET = 16;
static constexpr uint8_t GLO_CIPHERING = 0xDB;
static constexpr uint8_t DATA_NOTIFICATION = 0x0F;
static constexpr uint8_t TIMESTAMP_DATETIME = 0x0C;
static constexpr uint16_t MAX_MESSAGE_LENGTH = 512; // Maximum size of message (when having 2 bytes length in header).
// Provider specific quirks
static constexpr uint8_t NETZ_NOE_MAGIC_BYTE = 0x81; // Magic length byte used by Netz NOE
static constexpr uint8_t NETZ_NOE_EXPECTED_MESSAGE_LENGTH = 0xF8;
static constexpr uint8_t NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE = 0x20;
} // namespace esphome::dlms_meter

View File

@@ -0,0 +1,481 @@
#include "dlms_meter.h"
#include <cmath>
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
#include <bearssl/bearssl.h>
#elif defined(USE_ESP32)
#include "mbedtls/esp_config.h"
#include "mbedtls/gcm.h"
#endif
namespace esphome::dlms_meter {
static constexpr const char *TAG = "dlms_meter";
void DlmsMeterComponent::dump_config() {
const char *provider_name = this->provider_ == PROVIDER_NETZNOE ? "Netz NOE" : "Generic";
ESP_LOGCONFIG(TAG,
"DLMS Meter:\n"
" Provider: %s\n"
" Read Timeout: %u ms",
provider_name, this->read_timeout_);
#define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_);
DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, )
#define DLMS_METER_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s##_text_sensor_);
DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_LOG_TEXT_SENSOR, )
}
void DlmsMeterComponent::loop() {
// Read while data is available, netznoe uses two frames so allow 2x max frame length
size_t avail = this->available();
if (avail > 0) {
size_t remaining = MBUS_MAX_FRAME_LENGTH * 2 - this->receive_buffer_.size();
if (remaining == 0) {
ESP_LOGW(TAG, "Receive buffer full, dropping remaining bytes");
} else {
// Read all available bytes in batches to reduce UART call overhead.
// Cap reads to remaining buffer capacity.
if (avail > remaining) {
avail = remaining;
}
uint8_t buf[64];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
this->receive_buffer_.insert(this->receive_buffer_.end(), buf, buf + to_read);
this->last_read_ = millis();
}
}
}
if (!this->receive_buffer_.empty() && millis() - this->last_read_ > this->read_timeout_) {
this->mbus_payload_.clear();
if (!this->parse_mbus_(this->mbus_payload_))
return;
uint16_t message_length;
uint8_t systitle_length;
uint16_t header_offset;
if (!this->parse_dlms_(this->mbus_payload_, message_length, systitle_length, header_offset))
return;
if (message_length < DECODER_START_OFFSET || message_length > MAX_MESSAGE_LENGTH) {
ESP_LOGE(TAG, "DLMS: Message length invalid: %u", message_length);
this->receive_buffer_.clear();
return;
}
// Decrypt in place and then decode the OBIS codes
if (!this->decrypt_(this->mbus_payload_, message_length, systitle_length, header_offset))
return;
this->decode_obis_(&this->mbus_payload_[header_offset + DLMS_PAYLOAD_OFFSET], message_length);
}
}
bool DlmsMeterComponent::parse_mbus_(std::vector<uint8_t> &mbus_payload) {
ESP_LOGV(TAG, "Parsing M-Bus frames");
uint16_t frame_offset = 0; // Offset is used if the M-Bus message is split into multiple frames
while (frame_offset < this->receive_buffer_.size()) {
// Ensure enough bytes remain for the minimal intro header before accessing indices
if (this->receive_buffer_.size() - frame_offset < MBUS_HEADER_INTRO_LENGTH) {
ESP_LOGE(TAG, "MBUS: Not enough data for frame header (need %d, have %d)", MBUS_HEADER_INTRO_LENGTH,
(this->receive_buffer_.size() - frame_offset));
this->receive_buffer_.clear();
return false;
}
// Check start bytes
if (this->receive_buffer_[frame_offset + MBUS_START1_OFFSET] != START_BYTE_LONG_FRAME ||
this->receive_buffer_[frame_offset + MBUS_START2_OFFSET] != START_BYTE_LONG_FRAME) {
ESP_LOGE(TAG, "MBUS: Start bytes do not match");
this->receive_buffer_.clear();
return false;
}
// Both length bytes must be identical
if (this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET] !=
this->receive_buffer_[frame_offset + MBUS_LENGTH2_OFFSET]) {
ESP_LOGE(TAG, "MBUS: Length bytes do not match");
this->receive_buffer_.clear();
return false;
}
uint8_t frame_length = this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET]; // Get length of this frame
// Check if received data is enough for the given frame length
if (this->receive_buffer_.size() - frame_offset <
frame_length + 3) { // length field inside packet does not account for second start- + checksum- + stop- byte
ESP_LOGE(TAG, "MBUS: Frame too big for received data");
this->receive_buffer_.clear();
return false;
}
// Ensure we have full frame (header + payload + checksum + stop byte) before accessing stop byte
size_t required_total =
frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH; // payload + header + 2 footer bytes
if (this->receive_buffer_.size() - frame_offset < required_total) {
ESP_LOGE(TAG, "MBUS: Incomplete frame (need %d, have %d)", (unsigned int) required_total,
this->receive_buffer_.size() - frame_offset);
this->receive_buffer_.clear();
return false;
}
if (this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH - 1] !=
STOP_BYTE) {
ESP_LOGE(TAG, "MBUS: Invalid stop byte");
this->receive_buffer_.clear();
return false;
}
// Verify checksum: sum of all bytes starting at MBUS_HEADER_INTRO_LENGTH, take last byte
uint8_t checksum = 0; // use uint8_t so only the 8 least significant bits are stored
for (uint16_t i = 0; i < frame_length; i++) {
checksum += this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + i];
}
if (checksum != this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]) {
ESP_LOGE(TAG, "MBUS: Invalid checksum: %x != %x", checksum,
this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]);
this->receive_buffer_.clear();
return false;
}
mbus_payload.insert(mbus_payload.end(), &this->receive_buffer_[frame_offset + MBUS_FULL_HEADER_LENGTH],
&this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + frame_length]);
frame_offset += MBUS_HEADER_INTRO_LENGTH + frame_length + MBUS_FOOTER_LENGTH;
}
return true;
}
bool DlmsMeterComponent::parse_dlms_(const std::vector<uint8_t> &mbus_payload, uint16_t &message_length,
uint8_t &systitle_length, uint16_t &header_offset) {
ESP_LOGV(TAG, "Parsing DLMS header");
if (mbus_payload.size() < DLMS_HEADER_LENGTH + DLMS_HEADER_EXT_OFFSET) {
ESP_LOGE(TAG, "DLMS: Payload too short");
this->receive_buffer_.clear();
return false;
}
if (mbus_payload[DLMS_CIPHER_OFFSET] != GLO_CIPHERING) { // Only general-glo-ciphering is supported (0xDB)
ESP_LOGE(TAG, "DLMS: Unsupported cipher");
this->receive_buffer_.clear();
return false;
}
systitle_length = mbus_payload[DLMS_SYST_OFFSET];
if (systitle_length != 0x08) { // Only system titles with length of 8 are supported
ESP_LOGE(TAG, "DLMS: Unsupported system title length");
this->receive_buffer_.clear();
return false;
}
message_length = mbus_payload[DLMS_LENGTH_OFFSET];
header_offset = 0;
if (this->provider_ == PROVIDER_NETZNOE) {
// for some reason EVN seems to set the standard "length" field to 0x81 and then the actual length is in the next
// byte. Check some bytes to see if received data still matches expectation
if (message_length == NETZ_NOE_MAGIC_BYTE &&
mbus_payload[DLMS_LENGTH_OFFSET + 1] == NETZ_NOE_EXPECTED_MESSAGE_LENGTH &&
mbus_payload[DLMS_LENGTH_OFFSET + 2] == NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE) {
message_length = mbus_payload[DLMS_LENGTH_OFFSET + 1];
header_offset = 1;
} else {
ESP_LOGE(TAG, "Wrong Length - Security Control Byte sequence detected for provider EVN");
}
} else {
if (message_length == TWO_BYTE_LENGTH) {
message_length = encode_uint16(mbus_payload[DLMS_LENGTH_OFFSET + 1], mbus_payload[DLMS_LENGTH_OFFSET + 2]);
header_offset = DLMS_HEADER_EXT_OFFSET;
}
}
if (message_length < DLMS_LENGTH_CORRECTION) {
ESP_LOGE(TAG, "DLMS: Message length too short: %u", message_length);
this->receive_buffer_.clear();
return false;
}
message_length -= DLMS_LENGTH_CORRECTION; // Correct message length due to part of header being included in length
if (mbus_payload.size() - DLMS_HEADER_LENGTH - header_offset != message_length) {
ESP_LOGV(TAG, "DLMS: Length mismatch - payload=%d, header=%d, offset=%d, message=%d", mbus_payload.size(),
DLMS_HEADER_LENGTH, header_offset, message_length);
ESP_LOGE(TAG, "DLMS: Message has invalid length");
this->receive_buffer_.clear();
return false;
}
if (mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] != 0x21 &&
mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] !=
0x20) { // Only certain security suite is supported (0x21 || 0x20)
ESP_LOGE(TAG, "DLMS: Unsupported security control byte");
this->receive_buffer_.clear();
return false;
}
return true;
}
bool DlmsMeterComponent::decrypt_(std::vector<uint8_t> &mbus_payload, uint16_t message_length, uint8_t systitle_length,
uint16_t header_offset) {
ESP_LOGV(TAG, "Decrypting payload");
uint8_t iv[12]; // Reserve space for the IV, always 12 bytes
// Copy system title to IV (System title is before length; no header offset needed!)
// Add 1 to the offset in order to skip the system title length byte
memcpy(&iv[0], &mbus_payload[DLMS_SYST_OFFSET + 1], systitle_length);
memcpy(&iv[8], &mbus_payload[header_offset + DLMS_FRAMECOUNTER_OFFSET],
DLMS_FRAMECOUNTER_LENGTH); // Copy frame counter to IV
uint8_t *payload_ptr = &mbus_payload[header_offset + DLMS_PAYLOAD_OFFSET];
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
br_gcm_context gcm_ctx;
br_aes_ct_ctr_keys bc;
br_aes_ct_ctr_init(&bc, this->decryption_key_.data(), this->decryption_key_.size());
br_gcm_init(&gcm_ctx, &bc.vtable, br_ghash_ctmul32);
br_gcm_reset(&gcm_ctx, iv, sizeof(iv));
br_gcm_flip(&gcm_ctx);
br_gcm_run(&gcm_ctx, 0, payload_ptr, message_length);
#elif defined(USE_ESP32)
size_t outlen = 0;
mbedtls_gcm_context gcm_ctx;
mbedtls_gcm_init(&gcm_ctx);
mbedtls_gcm_setkey(&gcm_ctx, MBEDTLS_CIPHER_ID_AES, this->decryption_key_.data(), this->decryption_key_.size() * 8);
mbedtls_gcm_starts(&gcm_ctx, MBEDTLS_GCM_DECRYPT, iv, sizeof(iv));
auto ret = mbedtls_gcm_update(&gcm_ctx, payload_ptr, message_length, payload_ptr, message_length, &outlen);
mbedtls_gcm_free(&gcm_ctx);
if (ret != 0) {
ESP_LOGE(TAG, "Decryption failed with error: %d", ret);
this->receive_buffer_.clear();
return false;
}
#else
#error "Invalid Platform"
#endif
if (payload_ptr[0] != DATA_NOTIFICATION || payload_ptr[5] != TIMESTAMP_DATETIME) {
ESP_LOGE(TAG, "OBIS: Packet was decrypted but data is invalid");
this->receive_buffer_.clear();
return false;
}
ESP_LOGV(TAG, "Decrypted payload: %d bytes", message_length);
return true;
}
void DlmsMeterComponent::decode_obis_(uint8_t *plaintext, uint16_t message_length) {
ESP_LOGV(TAG, "Decoding payload");
MeterData data{};
uint16_t current_position = DECODER_START_OFFSET;
bool power_factor_found = false;
while (current_position + OBIS_CODE_OFFSET <= message_length) {
if (plaintext[current_position + OBIS_TYPE_OFFSET] != DataType::OCTET_STRING) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS header type: %x", plaintext[current_position + OBIS_TYPE_OFFSET]);
this->receive_buffer_.clear();
return;
}
uint8_t obis_code_length = plaintext[current_position + OBIS_LENGTH_OFFSET];
if (obis_code_length != OBIS_CODE_LENGTH_STANDARD && obis_code_length != OBIS_CODE_LENGTH_EXTENDED) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS header length: %x", obis_code_length);
this->receive_buffer_.clear();
return;
}
if (current_position + OBIS_CODE_OFFSET + obis_code_length > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for OBIS code");
this->receive_buffer_.clear();
return;
}
uint8_t *obis_code = &plaintext[current_position + OBIS_CODE_OFFSET];
uint8_t obis_medium = obis_code[OBIS_A];
uint16_t obis_cd = encode_uint16(obis_code[OBIS_C], obis_code[OBIS_D]);
bool timestamp_found = false;
bool meter_number_found = false;
if (this->provider_ == PROVIDER_NETZNOE) {
// Do not advance Position when reading the Timestamp at DECODER_START_OFFSET
if ((obis_code_length == OBIS_CODE_LENGTH_EXTENDED) && (current_position == DECODER_START_OFFSET)) {
timestamp_found = true;
} else if (power_factor_found) {
meter_number_found = true;
power_factor_found = false;
} else {
current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code and position
}
} else {
current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code, position and type
}
if (!timestamp_found && !meter_number_found && obis_medium != Medium::ELECTRICITY &&
obis_medium != Medium::ABSTRACT) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS medium: %x", obis_medium);
this->receive_buffer_.clear();
return;
}
if (current_position >= message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for data type");
this->receive_buffer_.clear();
return;
}
float value = 0.0f;
uint8_t value_size = 0;
uint8_t data_type = plaintext[current_position];
current_position++;
switch (data_type) {
case DataType::DOUBLE_LONG_UNSIGNED: {
value_size = 4;
if (current_position + value_size > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for DOUBLE_LONG_UNSIGNED");
this->receive_buffer_.clear();
return;
}
value = encode_uint32(plaintext[current_position + 0], plaintext[current_position + 1],
plaintext[current_position + 2], plaintext[current_position + 3]);
current_position += value_size;
break;
}
case DataType::LONG_UNSIGNED: {
value_size = 2;
if (current_position + value_size > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for LONG_UNSIGNED");
this->receive_buffer_.clear();
return;
}
value = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
current_position += value_size;
break;
}
case DataType::OCTET_STRING: {
uint8_t data_length = plaintext[current_position];
current_position++; // Advance past string length
if (current_position + data_length > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for OCTET_STRING");
this->receive_buffer_.clear();
return;
}
// Handle timestamp (normal OBIS code or NETZNOE special case)
if (obis_cd == OBIS_TIMESTAMP || timestamp_found) {
if (data_length < 8) {
ESP_LOGE(TAG, "OBIS: Timestamp data too short: %u", data_length);
this->receive_buffer_.clear();
return;
}
uint16_t year = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
uint8_t month = plaintext[current_position + 2];
uint8_t day = plaintext[current_position + 3];
uint8_t hour = plaintext[current_position + 5];
uint8_t minute = plaintext[current_position + 6];
uint8_t second = plaintext[current_position + 7];
if (year > 9999 || month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59) {
ESP_LOGE(TAG, "Invalid timestamp values: %04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour, minute,
second);
this->receive_buffer_.clear();
return;
}
snprintf(data.timestamp, sizeof(data.timestamp), "%04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour,
minute, second);
} else if (meter_number_found) {
snprintf(data.meternumber, sizeof(data.meternumber), "%.*s", data_length, &plaintext[current_position]);
}
current_position += data_length;
break;
}
default:
ESP_LOGE(TAG, "OBIS: Unsupported OBIS data type: %x", data_type);
this->receive_buffer_.clear();
return;
}
// Skip break after data
if (this->provider_ == PROVIDER_NETZNOE) {
// Don't skip the break on the first timestamp, as there's none
if (!timestamp_found) {
current_position += 2;
}
} else {
current_position += 2;
}
// Check for additional data (scaler-unit structure)
if (current_position < message_length && plaintext[current_position] == DataType::INTEGER) {
// Apply scaler: real_value = raw_value × 10^scaler
if (current_position + 1 < message_length) {
int8_t scaler = static_cast<int8_t>(plaintext[current_position + 1]);
if (scaler != 0) {
value *= powf(10.0f, scaler);
}
}
// on EVN Meters there is no additional break
if (this->provider_ == PROVIDER_NETZNOE) {
current_position += 4;
} else {
current_position += 6;
}
}
// Handle numeric values (LONG_UNSIGNED and DOUBLE_LONG_UNSIGNED)
if (value_size > 0) {
switch (obis_cd) {
case OBIS_VOLTAGE_L1:
data.voltage_l1 = value;
break;
case OBIS_VOLTAGE_L2:
data.voltage_l2 = value;
break;
case OBIS_VOLTAGE_L3:
data.voltage_l3 = value;
break;
case OBIS_CURRENT_L1:
data.current_l1 = value;
break;
case OBIS_CURRENT_L2:
data.current_l2 = value;
break;
case OBIS_CURRENT_L3:
data.current_l3 = value;
break;
case OBIS_ACTIVE_POWER_PLUS:
data.active_power_plus = value;
break;
case OBIS_ACTIVE_POWER_MINUS:
data.active_power_minus = value;
break;
case OBIS_ACTIVE_ENERGY_PLUS:
data.active_energy_plus = value;
break;
case OBIS_ACTIVE_ENERGY_MINUS:
data.active_energy_minus = value;
break;
case OBIS_REACTIVE_ENERGY_PLUS:
data.reactive_energy_plus = value;
break;
case OBIS_REACTIVE_ENERGY_MINUS:
data.reactive_energy_minus = value;
break;
case OBIS_POWER_FACTOR:
data.power_factor = value;
power_factor_found = true;
break;
default:
ESP_LOGW(TAG, "Unsupported OBIS code 0x%04X", obis_cd);
}
}
}
this->receive_buffer_.clear();
ESP_LOGI(TAG, "Received valid data");
this->publish_sensors(data);
this->status_clear_warning();
}
} // namespace esphome::dlms_meter

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