Compare commits

..

178 Commits

Author SHA1 Message Date
Jonathan Swoboda
ed4ebffa74 [x9c] Fix potentiometer unable to decrement (#13382)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 22:57:54 -05:00
J. Nick Koston
c213de4861 [mapping] Use stack buffers for numeric key error logging (#13299) 2026-01-19 17:42:08 -10:00
J. Nick Koston
6cf320fd60 [mqtt] Eliminate per-entity loop overhead and heap churn (#13356) 2026-01-19 17:41:55 -10:00
J. Nick Koston
aeea340bc6 [cs5460a] Remove unnecessary empty loop override (#13357) 2026-01-19 17:41:03 -10:00
J. Nick Koston
d0e50ed030 [lock] Extract set_state_ helper to reduce code duplication (#13359) 2026-01-19 17:40:51 -10:00
J. Nick Koston
280d460025 [statsd] Use direct appends and stack buffer instead of str_sprintf (#13223)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-19 17:40:20 -10:00
J. Nick Koston
ea70faf642 [debug] Use shared buf_append_printf helper from core (#13260) 2026-01-19 17:38:56 -10:00
J. Nick Koston
5d7b38b261 [ezo_pmp] Replace sprintf with bounds-checked snprintf (#13304) 2026-01-19 17:38:22 -10:00
J. Nick Koston
e88093ca60 [am43][lightwaverf][rf_bridge][spi_led_strip] Replace sprintf with safe alternatives (#13302) 2026-01-19 17:38:08 -10:00
J. Nick Koston
b48d4ab785 [mqtt] Reduce heap allocations in publish path (#13372) 2026-01-19 17:37:54 -10:00
J. Nick Koston
8ade9dfc10 [shtcx] Use LogString for type to_string to save RAM on ESP8266 (#13370) 2026-01-19 17:37:33 -10:00
J. Nick Koston
4e0e7796de [mqtt] Remove unnecessary defer in ESP8266 on_message callback (#13373) 2026-01-19 17:37:19 -10:00
J. Nick Koston
62b6c9bf7c [esp32_ble] Deprecate ESPBTUUID::to_string() in favor of heap-free to_str() (#13376) 2026-01-19 17:37:03 -10:00
J. Nick Koston
b5fe271d6b [sprinkler] Disable loops when idle to reduce CPU overhead (#13381) 2026-01-19 17:36:47 -10:00
J. Nick Koston
5d787e2512 [sprinkler] Eliminate std::string heap allocations (#13379) 2026-01-19 17:35:58 -10:00
J. Nick Koston
8998ef0bc3 [network] Deprecate IPAddress::str() in favor of heap-free str_to() (#13378) 2026-01-19 17:35:32 -10:00
J. Nick Koston
8ec31dd769 [voice_assistant] Deprecate Timer::to_string() in favor of heap-free to_str() (#13377) 2026-01-19 17:35:19 -10:00
J. Nick Koston
0193464f92 [dsmr] Avoid std::string allocation for decryption key (#13375) 2026-01-19 17:34:49 -10:00
Jonathan Swoboda
1996bc425f [core] Fix state leakage and module caching when processing multiple configurations (#13368)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:46:24 -05:00
Clyde Stubbs
a0d3d54d69 [mipi_spi] Add variants of ESP32-2432S028 displays (#13340) 2026-01-20 05:13:36 +11:00
J. Nick Koston
ee264d0fd4 [anova] Replace sprintf with bounds-checked alternatives (#13303) 2026-01-18 23:57:42 -10:00
J. Nick Koston
892e9b006f [api] Use MAX_STATE_LEN constant for Home Assistant state buffer (#13278) 2026-01-18 23:57:27 -10:00
J. Nick Koston
f8bd4ef57d [template][event] Use StringRef for set_action and on_event triggers (#13328) 2026-01-18 22:22:57 -10:00
J. Nick Koston
bfcc0e26a3 [dfrobot_sen0395][pipsolar][sim800l][wl_134] Replace sprintf with snprintf/buf_append_printf (#13301)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-18 22:22:44 -10:00
J. Nick Koston
86a1b4cf69 [select][fan] Use StringRef for on_value/on_preset_set triggers to avoid heap allocation (#13324) 2026-01-18 19:51:11 -10:00
J. Nick Koston
d8a28f6fba [scheduler] Replace resize() with erase() to save ~ 436 bytes flash (#13214) 2026-01-18 18:54:30 -10:00
J. Nick Koston
e80a940222 [gdk101] Use stack buffer to eliminate heap allocation for firmware version (#13224) 2026-01-18 18:52:49 -10:00
J. Nick Koston
e99dbe05f7 [toshiba] Replace to_string with stack buffer in debug logging (#13296) 2026-01-18 18:52:34 -10:00
J. Nick Koston
f453a8d9a1 [dfrobot_sen0395] Reduce heap allocations in command building (#13219) 2026-01-18 18:44:56 -10:00
J. Nick Koston
126190d26a [ezo] Replace str_sprintf with stack-based formatting (#13218)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-18 18:44:41 -10:00
J. Nick Koston
e40201a98d [cse7766] Use stack buffer for verbose debug logging (#13217) 2026-01-18 18:44:27 -10:00
J. Nick Koston
8142f5db44 [zephyr] Avoid heap allocation in preferences key formatting (#13215) 2026-01-18 18:43:50 -10:00
J. Nick Koston
98ccab87a7 [tormatic] Use stack buffers instead of str_sprintf in debug methods (#13225) 2026-01-18 18:43:36 -10:00
J. Nick Koston
b9e72a8774 [daikin_arc] Fix undefined behavior in sprintf calls (#13279)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-18 18:43:19 -10:00
J. Nick Koston
d9fc625c6a [web_server] Simplify datetime formatting with buf_append_printf (#13281) 2026-01-18 18:43:05 -10:00
J. Nick Koston
dfbf79d6d6 [homeassistant] Use buf_append_printf for ESP8266 flash optimization (#13284) 2026-01-18 18:42:19 -10:00
J. Nick Koston
ea0fac96cb [core][mqtt] Add str_sanitize_to(), soft-deprecate str_sanitize() (#13233) 2026-01-18 18:42:04 -10:00
J. Nick Koston
3182222d60 [esp32_hosted] Use stack buffer instead of str_sprintf for version string (#13226) 2026-01-18 18:41:47 -10:00
J. Nick Koston
d8849b16f2 [gpio] Use buf_append_printf in dump_summary for ESP8266 flash optimization (#13283) 2026-01-18 18:41:34 -10:00
J. Nick Koston
635983f163 [uptime] Use buf_append_printf for ESP8266 flash optimization (#13282) 2026-01-18 18:41:19 -10:00
J. Nick Koston
6cbe672004 [tuya] Use buf_append_printf for ESP8266 flash optimization (#13287) 2026-01-18 18:41:07 -10:00
J. Nick Koston
226867b05c [esp8266] Use direct SDK calls instead of Arduino ESP class wrappers (#13353) 2026-01-18 18:40:53 -10:00
J. Nick Koston
67871a1683 [ccs811] Use buf_append_printf for buffer safety and ESP8266 flash optimization (#13300) 2026-01-18 18:40:14 -10:00
J. Nick Koston
f60c03e350 [syslog] Use buf_append_printf for ESP8266 flash optimization (#13286) 2026-01-18 18:39:53 -10:00
J. Nick Koston
eb66429144 [sml] Use stack buffers instead of str_sprintf (#13222)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-18 18:39:23 -10:00
J. Nick Koston
0f3bac5dd6 [nextion] Replace to_string with stack buffer and fix unsafe sprintf (#13295) 2026-01-18 18:37:29 -10:00
J. Nick Koston
5b92d0b89e [wiegand] Replace heap-allocating to_string with stack buffers (#13294) 2026-01-18 18:37:14 -10:00
J. Nick Koston
052b05df56 [tuya] Replace unsafe sprintf with snprintf in light color formatting (#13292) 2026-01-18 18:37:02 -10:00
J. Nick Koston
7b0db659d1 [rc522_spi] Replace unsafe sprintf with buf_append_printf (#13291) 2026-01-18 18:36:46 -10:00
J. Nick Koston
2f7270cf8f [uart] Replace unsafe sprintf with buf_append_printf in debugger (#13288) 2026-01-18 18:36:32 -10:00
J. Nick Koston
b44727aee6 [socket] Eliminate heap allocations in set_sockaddr() (#13228) 2026-01-18 18:29:31 -10:00
J. Nick Koston
1a55254258 [status] Convert to PollingComponent to reduce CPU usage (#13342) 2026-01-18 18:28:24 -10:00
J. Nick Koston
baf2b0e3c9 [api] Fix truncation of Home Assistant attributes longer than 255 characters (#13348) 2026-01-18 18:23:11 -10:00
J. Nick Koston
680e92a226 [core] Add str_endswith_ignore_case to avoid heap allocation in audio file type detection (#13313) 2026-01-18 08:36:56 -10:00
J. Nick Koston
db0b32bfc9 [network] Fix IPAddress::str_to() to lowercase IPv6 hex digits (#13325) 2026-01-17 18:06:54 -10:00
J. Nick Koston
21794e28e5 [modbus_controller] Use stack buffers instead of heap-allocating string helpers (#13221)
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-01-17 17:26:51 -10:00
J. Nick Koston
728236270c [weikai] Replace bitset to_string with format_bin_to (#13297) 2026-01-17 15:53:01 -10:00
J. Nick Koston
01cdc4ed58 [core] Add fnv1_hash_extend() string overloads, use in atm90e32 (#13326) 2026-01-17 15:52:19 -10:00
J. Nick Koston
d6a0c8ffbb [template] Store alarm control panel codes in flash instead of heap (#13329) 2026-01-17 15:52:06 -10:00
J. Nick Koston
4cc0f874f7 [wireguard] Store configuration strings in flash instead of heap (#13331) 2026-01-17 15:51:26 -10:00
J. Nick Koston
ed58b9372f [template] Store text initial_value in flash and avoid heap allocation in setup (#13332) 2026-01-17 15:51:12 -10:00
J. Nick Koston
ee2a81923b [sun] Store text sensor format string in flash (#13335) 2026-01-17 15:51:01 -10:00
J. Nick Koston
0a1e7ee50b [pipsolar] Store command strings in flash (#13336) 2026-01-17 15:50:42 -10:00
J. Nick Koston
4d4283bcfa [udp] Store addresses in flash instead of heap (#13330) 2026-01-17 15:50:23 -10:00
J. Nick Koston
e4fb6988ff [web_server] Use ESPHOME_F for canHandle domain checks to reduce ESP8266 RAM (#13315)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2026-01-17 22:29:29 +00:00
J. Nick Koston
d31b733dce [light] Store color mode JSON strings in flash on ESP8266 (#13314) 2026-01-17 16:06:25 -06:00
Keith Burzinski
b25a2f8d8e [infrared][web_server] Implement initial web_server support (#13202)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-17 16:01:13 -06:00
J. Nick Koston
3f892711c7 [core][opentherm] Add format_bin_to(), soft-deprecate format_bin() (#13232) 2026-01-17 11:09:42 -10:00
Jonathan Swoboda
798d3bd956 Merge branch 'beta' into dev 2026-01-16 23:45:36 -05:00
Jonathan Swoboda
77df3933db Merge pull request #13309 from esphome/bump-2026.1.0b3
2026.1.0b3
2026-01-16 23:45:26 -05:00
Jonathan Swoboda
19514ccdf4 Bump version to 2026.1.0b3 2026-01-16 23:05:59 -05:00
Mike Ford
2947642ca5 [http_request] Unable to handle chunked responses (#7884)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-16 23:05:59 -05:00
Stuart Parmenter
60e333db08 [hub75] Bump esp-hub75 version to 0.3.0 (#13243) 2026-01-16 23:05:59 -05:00
J. Nick Koston
d8463f4813 [hmac_sha256] Replace unsafe sprintf with format_hex_to (#13290) 2026-01-16 23:05:59 -05:00
mrtoy-me
e1800d2fe2 [ntc, resistance] change log level to verbose (#13268) 2026-01-16 23:05:59 -05:00
J. Nick Koston
50aa4b1992 [esp32_ble_client] Reduce GATT data event logging to prevent firmware update failures (#13252) 2026-01-16 23:05:59 -05:00
J. Nick Koston
edb303e495 [api] Fix clock conflicts when multiple clients connected to homeassistant time (#13253) 2026-01-16 23:05:59 -05:00
J. Nick Koston
973fc4c5dc [dallas_temp] Use const char* for set_timeout to fix deprecation warning and heap churn (#13250) 2026-01-16 23:05:59 -05:00
J. Nick Koston
f88e8fc43b [sprinkler] Fix scheduler deprecation warnings and heap churn with FixedVector (#13251) 2026-01-16 23:05:59 -05:00
Jonathan Swoboda
d830787c71 Merge branch 'release' into dev 2026-01-16 22:49:39 -05:00
Jonathan Swoboda
c4c31a2e8e Merge branch 'release' into beta 2026-01-16 22:49:38 -05:00
Jonathan Swoboda
e6790f0042 Merge pull request #13308 from esphome/bump-2025.12.7
2025.12.7
2026-01-16 22:49:26 -05:00
Jonathan Swoboda
ec7f72e280 Bump version to 2025.12.7 2026-01-16 22:24:05 -05:00
J. Nick Koston
6f29dbd6f1 [api] Use subtraction for protobuf bounds checking (#13306) 2026-01-16 22:24:05 -05:00
Kevin Ahrendt
9caf78aa7e [i2s_audio] Bugfix: Buffer overflow in software volume control (#13190) 2026-01-16 22:24:05 -05:00
Mike Ford
1f4221abfa [http_request] Unable to handle chunked responses (#7884)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-16 22:18:48 -05:00
Stuart Parmenter
92808a09c7 [hub75] Bump esp-hub75 version to 0.3.0 (#13243) 2026-01-16 22:17:36 -05:00
J. Nick Koston
e54d5ee898 [hmac_sha256] Replace unsafe sprintf with format_hex_to (#13290) 2026-01-16 22:16:38 -05:00
J. Nick Koston
bbe1155518 [web_server] Skip defer on ESP8266 where callbacks already run in main loop (#13261) 2026-01-16 20:08:04 -06:00
J. Nick Koston
69d7b6e921 [api] Use subtraction for protobuf bounds checking (#13306) 2026-01-16 15:46:15 -10:00
Keith Burzinski
510c874061 [helpers] Remove base85 functions (#13266) 2026-01-17 01:23:41 +00:00
Keith Burzinski
f7ad324d81 [infrared, remote_base] Replace base85 with base64url for web server infrared transmissions (#13265) 2026-01-16 18:15:27 -06:00
Keith Burzinski
58a9e30017 [helpers] Add base64_decode_int32_vector function (#13289)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-16 23:05:19 +00:00
J. Nick Koston
52ac9e1861 [remote_base] Replace unsafe sprintf with buf_append_printf; fix buffer overflow (#13257)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-16 16:56:47 -06:00
Clyde Stubbs
c5e4a60884 [select] Add condition for testing select option (#13267)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-01-17 08:35:40 +11:00
dependabot[bot]
a680884138 Bump ruff from 0.14.12 to 0.14.13 (#13275)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-16 20:29:02 +00:00
Jonathan Swoboda
6832efbacc Add Claude Code PR workflow skill (#13271)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 10:24:28 -10:00
dependabot[bot]
3057a0484f Bump actions/cache from 5.0.1 to 5.0.2 in /.github/actions/restore-python (#13277)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-16 09:36:42 -10:00
dependabot[bot]
bc78f80f77 Bump actions/cache from 5.0.1 to 5.0.2 (#13276)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-16 09:36:29 -10:00
J. Nick Koston
916b028fb2 [mqtt] Replace sprintf with snprintf for friendly name hash (#13262) 2026-01-16 08:30:22 -10:00
mrtoy-me
16adae7359 [ntc, resistance] change log level to verbose (#13268) 2026-01-16 10:19:09 -05:00
Remco van Essen
4906f87751 [mipi_dsi] add JC8012P4A1 (#13241) 2026-01-16 21:17:32 +11:00
Keith Burzinski
5b37d2fb27 [helpers] Support base64url encoding (#13264) 2026-01-16 08:55:24 +00:00
J. Nick Koston
68affe0b9c [core] Add --device hint when DNS resolution fails (#13240) 2026-01-15 18:55:32 -10:00
J. Nick Koston
8263a8273f [debug] Add min_free heap sensor for ESP32 and LibreTiny, add fragmentation for ESP32 (#13231) 2026-01-15 18:08:26 -10:00
Keith Burzinski
14b7539094 [infrared, remote_base] Optimize IR transmit path for web_server base85 data (#13238) 2026-01-15 22:04:21 -06:00
J. Nick Koston
b37cb812a7 [core] Add buf_append_printf helper for safe buffer formatting (#13258) 2026-01-15 22:03:11 -06:00
J. Nick Koston
42491569c8 [analyze_memory] Add nRF52/Zephyr platform support for memory analysis (#13249) 2026-01-15 17:53:53 -10:00
J. Nick Koston
b1230ec6bb [esp32_ble_client] Reduce GATT data event logging to prevent firmware update failures (#13252) 2026-01-15 16:49:19 -10:00
J. Nick Koston
4eda9e965f [api] Fix clock conflicts when multiple clients connected to homeassistant time (#13253) 2026-01-15 16:49:01 -10:00
J. Nick Koston
d2528af649 [dallas_temp] Use const char* for set_timeout to fix deprecation warning and heap churn (#13250) 2026-01-15 16:48:44 -10:00
Keith Burzinski
2eabc1b96b [helpers] Add base85 support (#13254)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-16 02:22:05 +00:00
J. Nick Koston
535c3eb2a2 [sprinkler] Fix scheduler deprecation warnings and heap churn with FixedVector (#13251) 2026-01-15 11:32:02 -10:00
Jonathan Swoboda
20f937692e Merge branch 'beta' into dev 2026-01-15 16:24:19 -05:00
Jonathan Swoboda
c2737ca3bb Merge pull request #13247 from esphome/bump-2026.1.0b2
2026.1.0b2
2026-01-15 16:24:08 -05:00
J. Nick Koston
00cc9e44b6 [analyze_memory] Fix ELF section mapping for RTL87xx and LN882X platforms (#13213) 2026-01-15 10:38:24 -10:00
Jonathan Swoboda
c151b2da67 Bump version to 2026.1.0b2 2026-01-15 15:26:04 -05:00
J. Nick Koston
dacd185afb [web_server][captive_portal] Change default compression from Brotli to gzip (#13246) 2026-01-15 15:26:04 -05:00
John Stenger
f88cf1b83a [qr_code] Allocate and free memory for QR code buffer (#13161)
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>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-15 15:26:04 -05:00
Jonathan Swoboda
3f6412ba07 [safe_mode] Detect bootloader rollback support at runtime (#13230)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 15:26:04 -05:00
J. Nick Koston
1ad0969099 [core] Fix ESP32-S2/S3 hardware SHA crash by aligning HashBase digest buffer (#13234)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-01-15 15:26:04 -05:00
J. Nick Koston
737c2b8732 [core] Fix platform subcomponents not filtering source files (#13208) 2026-01-15 15:26:04 -05:00
J. Nick Koston
9030dc9d4e [api] Fix state updates being sent to clients that did not subscribe (#13237) 2026-01-15 15:26:04 -05:00
J. Nick Koston
0b5a3506cc [core] Optimize and normalize entity state publishing logs with >> format (#13236) 2026-01-15 15:26:04 -05:00
Clyde Stubbs
3c63ff5e36 [image] Correctly handle dimensions in physical units (#13209) 2026-01-15 15:26:04 -05:00
dependabot[bot]
0427350101 Bump ruff from 0.14.11 to 0.14.12 (#13244)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-15 09:59:40 -10:00
J. Nick Koston
41dceb76ec [web_server][captive_portal] Change default compression from Brotli to gzip (#13246) 2026-01-15 19:56:35 +00:00
John Stenger
6380458d78 [qr_code] Allocate and free memory for QR code buffer (#13161)
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>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-15 14:18:08 -05:00
Jonathan Swoboda
0dc5a7c9a4 [safe_mode] Detect bootloader rollback support at runtime (#13230)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 14:17:00 -05:00
J. Nick Koston
9003844eda [core] Fix ESP32-S2/S3 hardware SHA crash by aligning HashBase digest buffer (#13234)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-01-15 18:29:11 +00:00
J. Nick Koston
22a4ec69c2 [core] Fix platform subcomponents not filtering source files (#13208) 2026-01-15 07:38:44 -10:00
J. Nick Koston
9d42bfd161 [api] Fix state updates being sent to clients that did not subscribe (#13237) 2026-01-15 07:38:18 -10:00
J. Nick Koston
49c881d067 [core] Optimize and normalize entity state publishing logs with >> format (#13236) 2026-01-15 10:13:05 +00:00
J. Nick Koston
78aee4f498 [web_server] Remove unused button_state_json_generator (#13235) 2026-01-14 23:48:55 -06:00
Clyde Stubbs
9da2c08f36 [image] Correctly handle dimensions in physical units (#13209) 2026-01-15 03:27:26 +00:00
J. Nick Koston
03f3deff41 [lvgl] Use stack buffer for event code formatting, document justified str_sprintf usage (#13220)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-01-15 01:24:42 +00:00
dependabot[bot]
f1e5d3a39a Bump resvg-py from 0.2.5 to 0.2.6 (#13211)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-14 10:40:26 -10:00
Jonathan Swoboda
2f6863230d Merge branch 'beta' into dev 2026-01-14 10:52:28 -05:00
Jonathan Swoboda
0de91e6648 Merge pull request #13206 from esphome/bump-2026.1.0b1
2026.1.0b1
2026-01-14 10:52:13 -05:00
Jonathan Swoboda
f44036310c Bump version to 2026.2.0-dev 2026-01-14 09:19:45 -05:00
Jonathan Swoboda
66b4af1777 Bump version to 2026.1.0b1 2026-01-14 09:19:45 -05:00
J. Nick Koston
068b497b9b [web_server] Store method/domain comparison strings in flash on ESP8266 (#13205)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 09:18:17 -05:00
J. Nick Koston
d6fa1d6e5f [ethernet_info] Convert to event-driven IP state listener pattern (#13203)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 09:17:47 -05:00
J. Nick Koston
d5f557ad1c [scheduler] Eliminate heap allocations for std::string names and add uint32_t ID API (#13200) 2026-01-14 09:15:31 -05:00
tomaszduda23
9c5f4e5288 [usb_cdc_acm] move esp32 implementation to new file (#12824)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2026-01-14 04:07:18 -06:00
J. Nick Koston
c8cc29a991 [api] Reduce batch RAM usage by 33% via switch dispatch (#13199) 2026-01-14 03:58:06 +00:00
J. Nick Koston
8b49d465f8 [bh1750] Eliminate heap allocations by replacing callbacks with state machine (#11950) 2026-01-13 17:44:43 -10:00
J. Nick Koston
47ee2f4ad9 [wifi] Use StaticVector for WiFi listeners with per-type compile-time sizing (#13197) 2026-01-14 02:20:39 +00:00
J. Nick Koston
2793e33baf [logger] Use StaticVector for log listeners with compile-time sizing (#13196)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-13 15:43:17 -10:00
J. Nick Koston
5dfdd05122 [logger] Use RAII guards for recursion protection and optimize hot path (#13194)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-13 15:43:02 -10:00
Cougar
be12e3667a [ssd1306_i2c] fix "SSD1306 72x40" display initialization (add SSD1306B Iref setup) (#13148) 2026-01-13 18:30:15 -05:00
Clyde Stubbs
52c631384a [epaper_spi] Add Waveshare 2.13v3 (#13117) 2026-01-13 18:28:24 -05:00
Jonathan Swoboda
45e000f091 [ota] Mark partition valid when OTA begins to prevent rollback blocking (#13195)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 18:27:45 -05:00
tomaszduda23
e45cad45fe [nrf52,zigbee] Add binary output as switch (#13083)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-13 17:39:28 -05:00
J. Nick Koston
3d74d1e7f0 [libretiny] Regenerate boards, enable Cortex-M4 atomics, and consolidate platform code (#13191) 2026-01-13 21:39:11 +00:00
J. Nick Koston
a060d1d044 [wifi] Fix ESP8266 disconnect callback order to set error flag before notifying listeners (#13189) 2026-01-13 11:33:36 -10:00
Kevin Ahrendt
733f57da50 [i2s_audio] Bugfix: Buffer overflow in software volume control (#13190) 2026-01-13 09:42:36 -10:00
dependabot[bot]
4d96c60696 Bump yamllint from 1.37.1 to 1.38.0 (#13192)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 09:36:58 -10:00
J. Nick Koston
3d40979c96 [mqtt] Avoid intermediate string allocations in publish calls (#13174) 2026-01-13 08:05:04 -10:00
J. Nick Koston
7fed9144a6 [api] Use stack buffer for VERY_VERBOSE proto message dumps (#13176) 2026-01-13 08:04:48 -10:00
J. Nick Koston
7abb374f2a [improv_serial] Use stack buffers for webserver URL formatting (#13175) 2026-01-13 08:04:33 -10:00
Jonathan Swoboda
5d90f170e5 Merge branch 'release' into dev 2026-01-13 11:55:58 -05:00
Jonathan Swoboda
6e01c4f86e Merge pull request #13188 from esphome/bump-2025.12.6
2025.12.6
2026-01-13 11:55:44 -05:00
Jonathan Swoboda
f4c17e15ea Bump version to 2025.12.6 2026-01-13 11:01:21 -05:00
J. Nick Koston
d6507ce329 [esphome] Fix OTA backend abort not being called on error (#13182) 2026-01-13 11:01:21 -05:00
Jonathan Swoboda
9504e92458 [remote_transmitter] Fix ESP8266 timing by using busy loop (#13172)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Jonathan Swoboda
3911991de2 [packet_transport] Fix packet size check to account for round4 padding (#13165)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Jonathan Swoboda
dede47477b [ltr_als_ps] Remove incorrect device_class from count sensors (#13167)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Jonathan Swoboda
dca8def0f2 [seeed_mr24hpc1] Add ifdef guards for conditional entity types (#13147)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Samuel Sieb
a1727a8901 [espnow] fix channel validation (#13057) 2026-01-13 11:01:20 -05:00
Samuel Sieb
48f5296d24 [ld24xx] add id to support extending (#13183)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-01-12 22:32:20 -10:00
Samuel Sieb
1327776d5b [bme68x_bsec2] use EntityBase instead of Component for the id (#13185)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-01-12 22:32:11 -10:00
J. Nick Koston
df4a3e8915 [socket] Call lwip_read/lwip_write directly on ESP32 to reduce network I/O latency (#13179) 2026-01-13 01:47:11 -06:00
Keith Burzinski
6823e17b3b [ir_rf_proxy] New component (#12985)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-13 07:44:24 +00:00
J. Nick Koston
675103bed0 [esphome] Fix OTA backend abort not being called on error (#13182) 2026-01-12 20:55:40 -10:00
J. Nick Koston
6c043be4d3 [ci] Add format_hex_pretty to heap-allocating helper lint check (#13178) 2026-01-12 20:55:23 -10:00
Rodrigo Martín
e9469cbe48 [mqtt] templatable state and command topics (#12441)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-12 17:40:27 -10:00
dependabot[bot]
5890cdf69a Bump github/codeql-action from 4.31.9 to 4.31.10 (#13173)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 16:31:51 -10:00
369 changed files with 9523 additions and 4653 deletions

View File

@@ -0,0 +1,96 @@
---
name: pr-workflow
description: Create pull requests for esphome. Use when creating PRs, submitting changes, or preparing contributions.
allowed-tools: Read, Bash, Glob, Grep
---
# ESPHome PR Workflow
When creating a pull request for esphome, follow these steps:
## 1. Create Branch from Upstream
Always base your branch on **upstream** (not origin/fork) to ensure you have the latest code:
```bash
git fetch upstream
git checkout -b <branch-name> upstream/dev
```
## 2. Read the PR Template
Before creating a PR, read `.github/PULL_REQUEST_TEMPLATE.md` to understand required fields.
## 3. Create the PR
Use `gh pr create` with the **full template** filled in. Never skip or abbreviate sections.
Required fields:
- **What does this implement/fix?**: Brief description of changes
- **Types of changes**: Check ONE appropriate box (Bugfix, New feature, Breaking change, etc.)
- **Related issue**: Use `fixes <link>` syntax if applicable
- **Pull request in esphome-docs**: Link if docs are needed
- **Test Environment**: Check platforms you tested on
- **Example config.yaml**: Include working example YAML
- **Checklist**: Verify code is tested and tests added
## 4. Example PR Body
```markdown
# What does this implement/fix?
<describe your changes here>
## Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Developer breaking change (an API change that could break external components)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Other
**Related issue or feature (if applicable):**
- fixes https://github.com/esphome/esphome/issues/XXX
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):**
- esphome/esphome-docs#XXX
## Test Environment
- [x] ESP32
- [x] ESP32 IDF
- [ ] ESP8266
- [ ] RP2040
- [ ] BK72xx
- [ ] RTL87xx
- [ ] LN882x
- [ ] nRF52840
## Example entry for `config.yaml`:
```yaml
# Example config.yaml
component_name:
id: my_component
option: value
```
## Checklist:
- [x] The code change is tested and works locally.
- [x] Tests have been added to verify that the new code works (under `tests/` folder).
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
```
## 5. Push and Create PR
```bash
git push -u origin <branch-name>
gh pr create --repo esphome/esphome --base dev --title "[component] Brief description"
```
Title should be prefixed with the component name in brackets, e.g. `[safe_mode] Add feature`.

View File

@@ -27,6 +27,7 @@
- [ ] RP2040 - [ ] RP2040
- [ ] BK72xx - [ ] BK72xx
- [ ] RTL87xx - [ ] RTL87xx
- [ ] LN882x
- [ ] nRF52840 - [ ] nRF52840
## Example entry for `config.yaml`: ## Example entry for `config.yaml`:

View File

@@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View File

@@ -47,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@@ -157,7 +157,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache - name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -193,7 +193,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Restore components graph cache - name: Restore components graph cache
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: .temp/components_graph.json path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -223,7 +223,7 @@ jobs:
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
- name: Save components graph cache - name: Save components graph cache
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: .temp/components_graph.json path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -245,7 +245,7 @@ jobs:
python-version: "3.13" python-version: "3.13"
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -334,14 +334,14 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
@@ -413,14 +413,14 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -502,14 +502,14 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -735,7 +735,7 @@ jobs:
- name: Restore cached memory analysis - name: Restore cached memory analysis
id: cache-memory-analysis id: cache-memory-analysis
if: steps.check-script.outputs.skip != 'true' if: steps.check-script.outputs.skip != 'true'
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: memory-analysis-target.json path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }} key: ${{ steps.cache-key.outputs.cache-key }}
@@ -759,7 +759,7 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
@@ -800,7 +800,7 @@ jobs:
- name: Save memory analysis to cache - name: Save memory analysis to cache
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success' if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: memory-analysis-target.json path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }} key: ${{ steps.cache-key.outputs.cache-key }}
@@ -847,7 +847,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio - name: Cache platformio
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}

View File

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

View File

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

View File

@@ -255,6 +255,7 @@ esphome/components/inkplate/* @jesserockz @JosipKuci
esphome/components/integration/* @OttoWinter esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931 esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core esphome/components/interval/* @esphome/core
esphome/components/ir_rf_proxy/* @kbx81
esphome/components/jsn_sr04t/* @Mafus1 esphome/components/jsn_sr04t/* @Mafus1
esphome/components/json/* @esphome/core esphome/components/json/* @esphome/core
esphome/components/kamstrup_kmp/* @cfeenstra1024 esphome/components/kamstrup_kmp/* @cfeenstra1024

View File

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

View File

@@ -1,5 +1,6 @@
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
import argparse import argparse
from collections.abc import Callable
from datetime import datetime from datetime import datetime
import functools import functools
import getpass import getpass
@@ -222,8 +223,13 @@ def choose_upload_log_host(
else: else:
resolved.append(device) resolved.append(device)
if not resolved: if not resolved:
if CORE.dashboard:
hint = "If you know the IP, set 'use_address' in your network config."
else:
hint = "If you know the IP, try --device <IP>"
raise EsphomeError( raise EsphomeError(
f"All specified devices {defaults} could not be resolved. Is the device connected to the network?" f"All specified devices {defaults} could not be resolved. "
f"Is the device connected to the network? {hint}"
) )
return resolved return resolved
@@ -931,11 +937,21 @@ def command_dashboard(args: ArgsProtocol) -> int | None:
return dashboard.start_dashboard(args) return dashboard.start_dashboard(args)
def command_update_all(args: ArgsProtocol) -> int | None: def run_multiple_configs(
files: list, command_builder: Callable[[str], list[str]]
) -> int:
"""Run a command for each configuration file in a subprocess.
Args:
files: List of configuration files to process.
command_builder: Callable that takes a file path and returns a command list.
Returns:
Number of failed files.
"""
import click import click
success = {} success = {}
files = list_yaml_files(args.configuration)
twidth = 60 twidth = 60
def print_bar(middle_text): def print_bar(middle_text):
@@ -945,17 +961,19 @@ def command_update_all(args: ArgsProtocol) -> int | None:
safe_print(f"{half_line}{middle_text}{half_line}") safe_print(f"{half_line}{middle_text}{half_line}")
for f in files: for f in files:
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}") f_path = Path(f) if not isinstance(f, Path) else f
if any(f_path.name == x for x in SECRETS_FILES):
_LOGGER.warning("Skipping secrets file %s", f_path)
continue
safe_print(f"Processing {color(AnsiFore.CYAN, str(f))}")
safe_print("-" * twidth) safe_print("-" * twidth)
safe_print() safe_print()
if CORE.dashboard:
rc = run_external_process( cmd = command_builder(f)
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" rc = run_external_process(*cmd)
)
else:
rc = run_external_process(
"esphome", "run", f, "--no-logs", "--device", "OTA"
)
if rc == 0: if rc == 0:
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}") print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
success[f] = True success[f] = True
@@ -970,6 +988,8 @@ def command_update_all(args: ArgsProtocol) -> int | None:
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]") print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
failed = 0 failed = 0
for f in files: for f in files:
if f not in success:
continue # Skipped file
if success[f]: if success[f]:
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}") safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
else: else:
@@ -978,6 +998,17 @@ def command_update_all(args: ArgsProtocol) -> int | None:
return failed return failed
def command_update_all(args: ArgsProtocol) -> int | None:
files = list_yaml_files(args.configuration)
def build_command(f):
if CORE.dashboard:
return ["esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"]
return ["esphome", "run", f, "--no-logs", "--device", "OTA"]
return run_multiple_configs(files, build_command)
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int: def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
import json import json
@@ -1528,38 +1559,48 @@ def run_esphome(argv):
_LOGGER.info("ESPHome %s", const.__version__) _LOGGER.info("ESPHome %s", const.__version__)
for conf_path in args.configuration: # Multiple configurations: use subprocesses to avoid state leakage
conf_path = Path(conf_path) # between compilations (e.g., LVGL touchscreen state in module globals)
if any(conf_path.name == x for x in SECRETS_FILES): if len(args.configuration) > 1:
_LOGGER.warning("Skipping secrets file %s", conf_path) # Build command by reusing argv, replacing all configs with single file
continue # argv[0] is the program path, skip it since we prefix with "esphome"
def build_command(f):
return (
["esphome"]
+ [arg for arg in argv[1:] if arg not in args.configuration]
+ [str(f)]
)
CORE.config_path = conf_path return run_multiple_configs(args.configuration, build_command)
CORE.dashboard = args.dashboard
# For logs command, skip updating external components # Single configuration
skip_external = args.command == "logs" conf_path = Path(args.configuration[0])
config = read_config( if any(conf_path.name == x for x in SECRETS_FILES):
dict(args.substitution) if args.substitution else {}, _LOGGER.warning("Skipping secrets file %s", conf_path)
skip_external_update=skip_external, return 0
)
if config is None:
return 2
CORE.config = config
if args.command not in POST_CONFIG_ACTIONS: CORE.config_path = conf_path
safe_print(f"Unknown command {args.command}") CORE.dashboard = args.dashboard
try: # For logs command, skip updating external components
rc = POST_CONFIG_ACTIONS[args.command](args, config) skip_external = args.command == "logs"
except EsphomeError as e: config = read_config(
_LOGGER.error(e, exc_info=args.verbose) dict(args.substitution) if args.substitution else {},
return 1 skip_external_update=skip_external,
if rc != 0: )
return rc if config is None:
return 2
CORE.config = config
CORE.reset() if args.command not in POST_CONFIG_ACTIONS:
return 0 safe_print(f"Unknown command {args.command}")
return 1
try:
return POST_CONFIG_ACTIONS[args.command](args, config)
except EsphomeError as e:
_LOGGER.error(e, exc_info=args.verbose)
return 1
def main(): def main():

View File

@@ -22,7 +22,7 @@ from .helpers import (
map_section_name, map_section_name,
parse_symbol_line, parse_symbol_line,
) )
from .toolchain import find_tool, run_tool from .toolchain import find_tool, resolve_tool_path, run_tool
if TYPE_CHECKING: if TYPE_CHECKING:
from esphome.platformio_api import IDEData from esphome.platformio_api import IDEData
@@ -132,6 +132,12 @@ class MemoryAnalyzer:
readelf_path = readelf_path or idedata.readelf_path readelf_path = readelf_path or idedata.readelf_path
_LOGGER.debug("Using toolchain paths from PlatformIO idedata") _LOGGER.debug("Using toolchain paths from PlatformIO idedata")
# Validate paths exist, fall back to find_tool if they don't
# This handles cases like Zephyr where cc_path doesn't include full path
# and the toolchain prefix may differ (e.g., arm-zephyr-eabi- vs arm-none-eabi-)
objdump_path = resolve_tool_path("objdump", objdump_path, objdump_path)
readelf_path = resolve_tool_path("readelf", readelf_path, objdump_path)
self.objdump_path = objdump_path or "objdump" self.objdump_path = objdump_path or "objdump"
self.readelf_path = readelf_path or "readelf" self.readelf_path = readelf_path or "readelf"
self.external_components = external_components or set() self.external_components = external_components or set()

View File

@@ -9,11 +9,61 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
# Maps standard section names to their various platform-specific variants # Maps standard section names to their various platform-specific variants
# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram) # Note: Order matters! More specific patterns (.bss) must come before general ones (.dram)
# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise # because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise
#
# Platform-specific sections:
# - ESP8266/ESP32: .iram*, .dram*
# - LibreTiny RTL87xx: .xip.code_* (flash), .ram.code_* (RAM)
# - LibreTiny BK7231: .itcm.code (fast RAM), .vectors (interrupt vectors)
# - LibreTiny LN882X: .flash_text, .flash_copy* (flash code)
# - Zephyr/nRF52: text, rodata, datas, bss (no leading dots)
SECTION_MAPPING = { SECTION_MAPPING = {
".text": frozenset([".text", ".iram"]), ".text": frozenset(
".rodata": frozenset([".rodata"]), [
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss" ".text",
".data": frozenset([".data", ".dram"]), ".iram",
# LibreTiny RTL87xx XIP (eXecute In Place) flash code
".xip.code",
# LibreTiny RTL87xx RAM code
".ram.code_text",
# LibreTiny BK7231 fast RAM code and vectors
".itcm.code",
".vectors",
# LibreTiny LN882X flash code
".flash_text",
".flash_copy",
# Zephyr/nRF52 sections (no leading dots)
"text",
"rom_start",
]
),
".rodata": frozenset(
[
".rodata",
# LibreTiny RTL87xx read-only data in RAM
".ram.code_rodata",
# Zephyr/nRF52 sections (no leading dots)
"rodata",
]
),
# .bss patterns - must be before .data to catch ".dram0.bss"
".bss": frozenset(
[
".bss",
# LibreTiny LN882X BSS
".bss_ram",
# Zephyr/nRF52 sections (no leading dots)
"bss",
"noinit",
]
),
".data": frozenset(
[
".data",
".dram",
# Zephyr/nRF52 sections (no leading dots)
"datas",
]
),
} }
# Section to ComponentMemory attribute mapping # Section to ComponentMemory attribute mapping

View File

@@ -94,13 +94,13 @@ def parse_symbol_line(line: str) -> tuple[str, str, int, str] | None:
return None return None
# Find section, size, and name # Find section, size, and name
# Try each part as a potential section name
for i, part in enumerate(parts): for i, part in enumerate(parts):
if not part.startswith("."): # Skip parts that are clearly flags, addresses, or other metadata
continue # Sections start with '.' (standard ELF) or are known section names (Zephyr)
section = map_section_name(part) section = map_section_name(part)
if not section: if not section:
break continue
# Need at least size field after section # Need at least size field after section
if i + 1 >= len(parts): if i + 1 >= len(parts):

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import os
from pathlib import Path from pathlib import Path
import subprocess import subprocess
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -17,10 +18,82 @@ TOOLCHAIN_PREFIXES = [
"xtensa-lx106-elf-", # ESP8266 "xtensa-lx106-elf-", # ESP8266
"xtensa-esp32-elf-", # ESP32 "xtensa-esp32-elf-", # ESP32
"xtensa-esp-elf-", # ESP32 (newer IDF) "xtensa-esp-elf-", # ESP32 (newer IDF)
"arm-zephyr-eabi-", # nRF52/Zephyr SDK
"arm-none-eabi-", # Generic ARM (RP2040, etc.)
"", # System default (no prefix) "", # System default (no prefix)
] ]
def _find_in_platformio_packages(tool_name: str) -> str | None:
"""Search for a tool in PlatformIO package directories.
This handles cases like Zephyr SDK where tools are installed in nested
directories that aren't in PATH.
Args:
tool_name: Name of the tool (e.g., "readelf", "objdump")
Returns:
Full path to the tool or None if not found
"""
# Get PlatformIO packages directory
platformio_home = Path(os.path.expanduser("~/.platformio/packages"))
if not platformio_home.exists():
return None
# Search patterns for toolchains that might contain the tool
# Order matters - more specific patterns first
search_patterns = [
# Zephyr SDK deeply nested structure (4 levels)
# e.g., toolchain-gccarmnoneeabi/zephyr-sdk-0.17.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-objdump
f"toolchain-*/*/*/bin/*-{tool_name}",
# Zephyr SDK nested structure (3 levels)
f"toolchain-*/*/bin/*-{tool_name}",
f"toolchain-*/bin/*-{tool_name}",
# Standard PlatformIO toolchain structure
f"toolchain-*/bin/*{tool_name}",
]
for pattern in search_patterns:
matches = list(platformio_home.glob(pattern))
if matches:
# Sort to get consistent results, prefer arm-zephyr-eabi over arm-none-eabi
matches.sort(key=lambda p: ("zephyr" not in str(p), str(p)))
tool_path = str(matches[0])
_LOGGER.debug("Found %s in PlatformIO packages: %s", tool_name, tool_path)
return tool_path
return None
def resolve_tool_path(
tool_name: str,
derived_path: str | None,
objdump_path: str | None = None,
) -> str | None:
"""Resolve a tool path, falling back to find_tool if derived path doesn't exist.
Args:
tool_name: Name of the tool (e.g., "objdump", "readelf")
derived_path: Path derived from idedata (may not exist for some platforms)
objdump_path: Path to objdump binary to derive other tool paths from
Returns:
Resolved path to the tool, or the original derived_path if it exists
"""
if derived_path and not Path(derived_path).exists():
found = find_tool(tool_name, objdump_path)
if found:
_LOGGER.debug(
"Derived %s path %s not found, using %s",
tool_name,
derived_path,
found,
)
return found
return derived_path
def find_tool( def find_tool(
tool_name: str, tool_name: str,
objdump_path: str | None = None, objdump_path: str | None = None,
@@ -28,7 +101,8 @@ def find_tool(
"""Find a toolchain tool by name. """Find a toolchain tool by name.
First tries to derive the tool path from objdump_path (if provided), First tries to derive the tool path from objdump_path (if provided),
then falls back to searching for platform-specific tools. then searches PlatformIO package directories (for cross-compile toolchains),
and finally falls back to searching for platform-specific tools in PATH.
Args: Args:
tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt") tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt")
@@ -47,7 +121,13 @@ def find_tool(
_LOGGER.debug("Found %s at: %s", tool_name, potential_path) _LOGGER.debug("Found %s at: %s", tool_name, potential_path)
return potential_path return potential_path
# Try platform-specific tools # Search in PlatformIO packages directory first (handles Zephyr SDK, etc.)
# This must come before PATH search because system tools (e.g., /usr/bin/objdump)
# are for the host architecture, not the target (ARM, Xtensa, etc.)
if found := _find_in_platformio_packages(tool_name):
return found
# Try platform-specific tools in PATH (fallback for when tools are installed globally)
for prefix in TOOLCHAIN_PREFIXES: for prefix in TOOLCHAIN_PREFIXES:
cmd = f"{prefix}{tool_name}" cmd = f"{prefix}{tool_name}"
try: try:

View File

@@ -69,6 +69,7 @@ from esphome.cpp_types import ( # noqa: F401
JsonObjectConst, JsonObjectConst,
Parented, Parented,
PollingComponent, PollingComponent,
StringRef,
arduino_json_ns, arduino_json_ns,
bool_, bool_,
const_char_ptr, const_char_ptr,

View File

@@ -31,7 +31,8 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->last_update_ = millis(); this->last_update_ = millis();
if (state != this->current_state_) { if (state != this->current_state_) {
auto prev_state = this->current_state_; auto prev_state = this->current_state_;
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)), ESP_LOGD(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state; this->current_state_ = state;
// Single state callback - triggers check get_state() for specific states // Single state callback - triggers check get_state() for specific states

View File

@@ -1,21 +1,12 @@
#include "am43_base.h" #include "am43_base.h"
#include "esphome/core/helpers.h"
#include <cstring> #include <cstring>
#include <cstdio>
namespace esphome { namespace esphome {
namespace am43 { namespace am43 {
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a}; const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
char buf[64];
memset(buf, 0, 64);
for (int i = 0; i < len; i++)
sprintf(&buf[i * 2], "%02x", data[i]);
std::string ret = buf;
return ret;
}
Am43Packet *Am43Encoder::get_battery_level_request() { Am43Packet *Am43Encoder::get_battery_level_request() {
uint8_t data = 0x1; uint8_t data = 0x1;
return this->encode_(0xA2, &data, 1); return this->encode_(0xA2, &data, 1);
@@ -73,7 +64,9 @@ Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length)
memcpy(&this->packet_.data[7], data, length); memcpy(&this->packet_.data[7], data, length);
this->packet_.length = length + 7; this->packet_.length = length + 7;
this->checksum_(); this->checksum_();
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str()); char hex_buf[format_hex_size(sizeof(this->packet_.data))];
ESP_LOGV("am43", "ENC(%d): 0x%s", this->packet_.length,
format_hex_to(hex_buf, this->packet_.data, this->packet_.length));
return &this->packet_; return &this->packet_;
} }
@@ -88,7 +81,8 @@ void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
this->has_set_state_response_ = false; this->has_set_state_response_ = false;
this->has_position_ = false; this->has_position_ = false;
this->has_pin_response_ = false; this->has_pin_response_ = false;
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str()); char hex_buf[format_hex_size(24)]; // Max expected packet size
ESP_LOGV("am43", "DEC(%d): 0x%s", length, format_hex_to(hex_buf, data, length));
if (length < 2 || data[0] != 0x9a) if (length < 2 || data[0] != 0x9a)
return; return;

View File

@@ -18,31 +18,31 @@ AnovaPacket *AnovaCodec::clean_packet_() {
AnovaPacket *AnovaCodec::get_read_device_status_request() { AnovaPacket *AnovaCodec::get_read_device_status_request() {
this->current_query_ = READ_DEVICE_STATUS; this->current_query_ = READ_DEVICE_STATUS;
sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_read_target_temp_request() { AnovaPacket *AnovaCodec::get_read_target_temp_request() {
this->current_query_ = READ_TARGET_TEMPERATURE; this->current_query_ = READ_TARGET_TEMPERATURE;
sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_read_current_temp_request() { AnovaPacket *AnovaCodec::get_read_current_temp_request() {
this->current_query_ = READ_CURRENT_TEMPERATURE; this->current_query_ = READ_CURRENT_TEMPERATURE;
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_read_unit_request() { AnovaPacket *AnovaCodec::get_read_unit_request() {
this->current_query_ = READ_UNIT; this->current_query_ = READ_UNIT;
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_read_data_request() { AnovaPacket *AnovaCodec::get_read_data_request() {
this->current_query_ = READ_DATA; this->current_query_ = READ_DATA;
sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
return this->clean_packet_(); return this->clean_packet_();
} }
@@ -50,25 +50,25 @@ AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
this->current_query_ = SET_TARGET_TEMPERATURE; this->current_query_ = SET_TARGET_TEMPERATURE;
if (this->fahrenheit_) if (this->fahrenheit_)
temperature = ctof(temperature); temperature = ctof(temperature);
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TARGET_TEMP, temperature);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) { AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
this->current_query_ = SET_UNIT; this->current_query_ = SET_UNIT;
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TEMP_UNIT, unit);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_start_request() { AnovaPacket *AnovaCodec::get_start_request() {
this->current_query_ = START; this->current_query_ = START;
sprintf((char *) this->packet_.data, CMD_START); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
return this->clean_packet_(); return this->clean_packet_();
} }
AnovaPacket *AnovaCodec::get_stop_request() { AnovaPacket *AnovaCodec::get_stop_request() {
this->current_query_ = STOP; this->current_query_ = STOP;
sprintf((char *) this->packet_.data, CMD_STOP); snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
return this->clean_packet_(); return this->clean_packet_();
} }

View File

@@ -4,6 +4,7 @@ import logging
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.logger import request_log_listener
from esphome.config_helpers import get_logger_level from esphome.config_helpers import get_logger_level
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@@ -326,6 +327,9 @@ async def to_code(config: ConfigType) -> None:
# Track controller registration for StaticVector sizing # Track controller registration for StaticVector sizing
CORE.register_controller() CORE.register_controller()
# Request a log listener slot for API log streaming
request_log_listener()
cg.add(var.set_port(config[CONF_PORT])) cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))

View File

@@ -265,8 +265,7 @@ void APIConnection::loop() {
// If we can't send the ping request directly (tx_buffer full), // If we can't send the ping request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority // schedule it at the front of the batch so it will be sent with priority
ESP_LOGW(TAG, "Buffer full, ping queued"); ESP_LOGW(TAG, "Buffer full, ping queued");
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE, this->schedule_message_front_(nullptr, PingRequest::MESSAGE_TYPE, PingRequest::ESTIMATED_SIZE);
PingRequest::ESTIMATED_SIZE);
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
} }
} }
@@ -305,7 +304,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return // If in log-only mode, just log and return
if (conn->flags_.log_only_mode) { if (conn->flags_.log_only_mode) {
conn->log_send_message_(msg.message_name(), msg.dump()); DumpBuffer dump_buf;
conn->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
return 1; // Return non-zero to indicate "success" for logging return 1; // Return non-zero to indicate "success" for logging
} }
#endif #endif
@@ -361,8 +361,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE,
BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE); BinarySensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -388,8 +388,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
#ifdef USE_COVER #ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) { bool APIConnection::send_cover_state(cover::Cover *cover) {
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE, return this->send_message_smart_(cover, CoverStateResponse::MESSAGE_TYPE, CoverStateResponse::ESTIMATED_SIZE);
CoverStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -429,8 +428,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#ifdef USE_FAN #ifdef USE_FAN
bool APIConnection::send_fan_state(fan::Fan *fan) { bool APIConnection::send_fan_state(fan::Fan *fan) {
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE, return this->send_message_smart_(fan, FanStateResponse::MESSAGE_TYPE, FanStateResponse::ESTIMATED_SIZE);
FanStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -481,8 +479,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) { bool APIConnection::send_light_state(light::LightState *light) {
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE, return this->send_message_smart_(light, LightStateResponse::MESSAGE_TYPE, LightStateResponse::ESTIMATED_SIZE);
LightStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -568,8 +565,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE, return this->send_message_smart_(sensor, SensorStateResponse::MESSAGE_TYPE, SensorStateResponse::ESTIMATED_SIZE);
SensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -597,8 +593,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch) { bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE, return this->send_message_smart_(a_switch, SwitchStateResponse::MESSAGE_TYPE, SwitchStateResponse::ESTIMATED_SIZE);
SwitchStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -632,8 +627,8 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, return this->send_message_smart_(text_sensor, TextSensorStateResponse::MESSAGE_TYPE,
TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE); TextSensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -657,8 +652,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) { bool APIConnection::send_climate_state(climate::Climate *climate) {
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE, return this->send_message_smart_(climate, ClimateStateResponse::MESSAGE_TYPE, ClimateStateResponse::ESTIMATED_SIZE);
ClimateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -753,8 +747,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool APIConnection::send_number_state(number::Number *number) { bool APIConnection::send_number_state(number::Number *number) {
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE, return this->send_message_smart_(number, NumberStateResponse::MESSAGE_TYPE, NumberStateResponse::ESTIMATED_SIZE);
NumberStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -788,8 +781,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool APIConnection::send_date_state(datetime::DateEntity *date) { bool APIConnection::send_date_state(datetime::DateEntity *date) {
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE, return this->send_message_smart_(date, DateStateResponse::MESSAGE_TYPE, DateStateResponse::ESTIMATED_SIZE);
DateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -817,8 +809,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool APIConnection::send_time_state(datetime::TimeEntity *time) { bool APIConnection::send_time_state(datetime::TimeEntity *time) {
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE, return this->send_message_smart_(time, TimeStateResponse::MESSAGE_TYPE, TimeStateResponse::ESTIMATED_SIZE);
TimeStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -846,8 +837,8 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, return this->send_message_smart_(datetime, DateTimeStateResponse::MESSAGE_TYPE,
DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE); DateTimeStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -877,8 +868,7 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
#ifdef USE_TEXT #ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text) { bool APIConnection::send_text_state(text::Text *text) {
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE, return this->send_message_smart_(text, TextStateResponse::MESSAGE_TYPE, TextStateResponse::ESTIMATED_SIZE);
TextStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -910,8 +900,7 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
#ifdef USE_SELECT #ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select) { bool APIConnection::send_select_state(select::Select *select) {
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE, return this->send_message_smart_(select, SelectStateResponse::MESSAGE_TYPE, SelectStateResponse::ESTIMATED_SIZE);
SelectStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -955,8 +944,7 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg
#ifdef USE_LOCK #ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock) { bool APIConnection::send_lock_state(lock::Lock *a_lock) {
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE, return this->send_message_smart_(a_lock, LockStateResponse::MESSAGE_TYPE, LockStateResponse::ESTIMATED_SIZE);
LockStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -996,8 +984,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
#ifdef USE_VALVE #ifdef USE_VALVE
bool APIConnection::send_valve_state(valve::Valve *valve) { bool APIConnection::send_valve_state(valve::Valve *valve) {
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE, return this->send_message_smart_(valve, ValveStateResponse::MESSAGE_TYPE, ValveStateResponse::ESTIMATED_SIZE);
ValveStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1031,8 +1018,8 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, return this->send_message_smart_(media_player, MediaPlayerStateResponse::MESSAGE_TYPE,
MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE); MediaPlayerStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1314,8 +1301,7 @@ void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE,
AlarmControlPanelStateResponse::MESSAGE_TYPE,
AlarmControlPanelStateResponse::ESTIMATED_SIZE); AlarmControlPanelStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
@@ -1368,8 +1354,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#ifdef USE_WATER_HEATER #ifdef USE_WATER_HEATER
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) { bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state, return this->send_message_smart_(water_heater, WaterHeaterStateResponse::MESSAGE_TYPE,
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE); WaterHeaterStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1418,10 +1404,11 @@ void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequ
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, StringRef event_type) { // Event is a special case - unlike other entities with simple state fields,
// get_last_event_type() returns StringRef pointing to null-terminated string literals from codegen // events store their state in a member accessed via obj->get_last_event_type()
this->send_message_smart_(event, MessageCreator(event_type.c_str()), EventResponse::MESSAGE_TYPE, void APIConnection::send_event(event::Event *event) {
EventResponse::ESTIMATED_SIZE); this->send_message_smart_(event, EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE,
event->get_last_event_type_index());
} }
uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn, uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
@@ -1472,8 +1459,7 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) { bool APIConnection::send_update_state(update::UpdateEntity *update) {
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE, return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE);
UpdateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1726,17 +1712,16 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
} }
// Create null-terminated state for callback (parse_number needs null-termination) // Create null-terminated state for callback (parse_number needs null-termination)
// HA state max length is 255, so 256 byte buffer covers all cases // HA state max length is 255 characters, but attributes can be much longer
char state_buf[256]; // Use stack buffer for common case (states), heap fallback for large attributes
size_t copy_len = msg.state.size(); size_t state_len = msg.state.size();
if (copy_len >= sizeof(state_buf)) { SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
if (state_len > 0) {
memcpy(state_buf, msg.state.c_str(), state_len);
} }
if (copy_len > 0) { state_buf[state_len] = '\0';
memcpy(state_buf, msg.state.c_str(), copy_len); it.callback(StringRef(state_buf, state_len));
}
state_buf[copy_len] = '\0';
it.callback(StringRef(state_buf, copy_len));
} }
} }
#endif #endif
@@ -1896,30 +1881,31 @@ void APIConnection::on_fatal_error() {
this->flags_.remove = true; this->flags_.remove = true;
} }
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t estimated_size) { uint8_t aux_data_index) {
// Check if we already have a message of this type for this entity // Check if we already have a message of this type for this entity
// This provides deduplication per entity/message_type combination // This provides deduplication per entity/message_type combination
// O(n) but optimized for RAM and not performance. // O(n) but optimized for RAM and not performance.
for (auto &item : items) { // Skip deduplication for events - they are edge-triggered, every occurrence matters
if (item.entity == entity && item.message_type == message_type) { #ifdef USE_EVENT
// Replace with new creator if (message_type != EventResponse::MESSAGE_TYPE)
item.creator = creator; #endif
return; {
for (const auto &item : items) {
if (item.entity == entity && item.message_type == message_type)
return; // Already queued
} }
} }
// No existing item found (or event), add new one
// No existing item found, add new one items.push_back({entity, message_type, estimated_size, aux_data_index});
items.emplace_back(entity, creator, message_type, estimated_size);
} }
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
uint8_t estimated_size) {
// Add high priority message and swap to front // Add high priority message and swap to front
// This avoids expensive vector::insert which shifts all elements // This avoids expensive vector::insert which shifts all elements
// Note: We only ever have one high-priority message at a time (ping OR disconnect) // Note: We only ever have one high-priority message at a time (ping OR disconnect)
// If we're disconnecting, pings are blocked, so this simple swap is sufficient // If we're disconnecting, pings are blocked, so this simple swap is sufficient
items.emplace_back(entity, creator, message_type, estimated_size); items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
if (items.size() > 1) { if (items.size() > 1) {
// Swap the new high-priority item to the front // Swap the new high-priority item to the front
std::swap(items.front(), items.back()); std::swap(items.front(), items.back());
@@ -1958,19 +1944,17 @@ void APIConnection::process_batch_() {
if (num_items == 1) { if (num_items == 1) {
const auto &item = this->deferred_batch_[0]; const auto &item = this->deferred_batch_[0];
// Let the creator calculate size and encode if it fits // Let dispatch_message_ calculate size and encode if it fits
uint16_t payload_size = uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits<uint16_t>::max(), true);
item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) { if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// Log messages after send attempt for VV debugging // Log message after send attempt for VV debugging
// It's safe to use the buffer for logging at this point regardless of send result
this->log_batch_item_(item); this->log_batch_item_(item);
#endif #endif
this->clear_batch_(); this->clear_batch_();
} else if (payload_size == 0) { } else if (payload_size == 0) {
// Message too large // Message too large to fit in available space
ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
this->clear_batch_(); this->clear_batch_();
} }
@@ -2015,9 +1999,9 @@ void APIConnection::process_batch_() {
// Process items and encode directly to buffer (up to our limit) // Process items and encode directly to buffer (up to our limit)
for (size_t i = 0; i < messages_to_process; i++) { for (size_t i = 0; i < messages_to_process; i++) {
const auto &item = this->deferred_batch_[i]; const auto &item = this->deferred_batch_[i];
// Try to encode message // Try to encode message via dispatch
// The creator will calculate overhead to determine if the message fits // The dispatch function calculates overhead to determine if the message fits
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); uint16_t payload_size = this->dispatch_message_(item, remaining_size, false);
if (payload_size == 0) { if (payload_size == 0) {
// Message won't fit, stop processing // Message won't fit, stop processing
@@ -2083,18 +2067,129 @@ void APIConnection::process_batch_() {
} }
} }
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, // Dispatch message encoding based on message_type
bool is_single, uint8_t message_type) const { // Switch assigns function pointer, single call site for smaller code size
uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size,
bool is_single) {
#ifdef USE_EVENT #ifdef USE_EVENT
// Special case: EventResponse uses const char * pointer // Events need aux_data_index to look up event type from entity
if (message_type == EventResponse::MESSAGE_TYPE) { if (item.message_type == EventResponse::MESSAGE_TYPE) {
auto *e = static_cast<event::Event *>(entity); // Skip if aux_data_index is invalid (should never happen in normal operation)
return APIConnection::try_send_event_response(e, StringRef(data_.const_char_ptr), conn, remaining_size, is_single); if (item.aux_data_index == DeferredBatch::AUX_DATA_UNUSED)
return 0;
auto *event = static_cast<event::Event *>(item.entity);
return try_send_event_response(event, StringRef::from_maybe_nullptr(event->get_event_type(item.aux_data_index)),
this, remaining_size, is_single);
} }
#endif #endif
// All other message types use function pointers // All other message types use function pointer lookup via switch
return data_.function_ptr(entity, conn, remaining_size, is_single); MessageCreatorPtr func = nullptr;
// Macros to reduce repetitive switch cases
#define CASE_STATE_INFO(entity_name, StateResp, InfoResp) \
case StateResp::MESSAGE_TYPE: \
func = &try_send_##entity_name##_state; \
break; \
case InfoResp::MESSAGE_TYPE: \
func = &try_send_##entity_name##_info; \
break;
#define CASE_INFO_ONLY(entity_name, InfoResp) \
case InfoResp::MESSAGE_TYPE: \
func = &try_send_##entity_name##_info; \
break;
switch (item.message_type) {
#ifdef USE_BINARY_SENSOR
CASE_STATE_INFO(binary_sensor, BinarySensorStateResponse, ListEntitiesBinarySensorResponse)
#endif
#ifdef USE_COVER
CASE_STATE_INFO(cover, CoverStateResponse, ListEntitiesCoverResponse)
#endif
#ifdef USE_FAN
CASE_STATE_INFO(fan, FanStateResponse, ListEntitiesFanResponse)
#endif
#ifdef USE_LIGHT
CASE_STATE_INFO(light, LightStateResponse, ListEntitiesLightResponse)
#endif
#ifdef USE_SENSOR
CASE_STATE_INFO(sensor, SensorStateResponse, ListEntitiesSensorResponse)
#endif
#ifdef USE_SWITCH
CASE_STATE_INFO(switch, SwitchStateResponse, ListEntitiesSwitchResponse)
#endif
#ifdef USE_BUTTON
CASE_INFO_ONLY(button, ListEntitiesButtonResponse)
#endif
#ifdef USE_TEXT_SENSOR
CASE_STATE_INFO(text_sensor, TextSensorStateResponse, ListEntitiesTextSensorResponse)
#endif
#ifdef USE_CLIMATE
CASE_STATE_INFO(climate, ClimateStateResponse, ListEntitiesClimateResponse)
#endif
#ifdef USE_NUMBER
CASE_STATE_INFO(number, NumberStateResponse, ListEntitiesNumberResponse)
#endif
#ifdef USE_DATETIME_DATE
CASE_STATE_INFO(date, DateStateResponse, ListEntitiesDateResponse)
#endif
#ifdef USE_DATETIME_TIME
CASE_STATE_INFO(time, TimeStateResponse, ListEntitiesTimeResponse)
#endif
#ifdef USE_DATETIME_DATETIME
CASE_STATE_INFO(datetime, DateTimeStateResponse, ListEntitiesDateTimeResponse)
#endif
#ifdef USE_TEXT
CASE_STATE_INFO(text, TextStateResponse, ListEntitiesTextResponse)
#endif
#ifdef USE_SELECT
CASE_STATE_INFO(select, SelectStateResponse, ListEntitiesSelectResponse)
#endif
#ifdef USE_LOCK
CASE_STATE_INFO(lock, LockStateResponse, ListEntitiesLockResponse)
#endif
#ifdef USE_VALVE
CASE_STATE_INFO(valve, ValveStateResponse, ListEntitiesValveResponse)
#endif
#ifdef USE_MEDIA_PLAYER
CASE_STATE_INFO(media_player, MediaPlayerStateResponse, ListEntitiesMediaPlayerResponse)
#endif
#ifdef USE_ALARM_CONTROL_PANEL
CASE_STATE_INFO(alarm_control_panel, AlarmControlPanelStateResponse, ListEntitiesAlarmControlPanelResponse)
#endif
#ifdef USE_WATER_HEATER
CASE_STATE_INFO(water_heater, WaterHeaterStateResponse, ListEntitiesWaterHeaterResponse)
#endif
#ifdef USE_CAMERA
CASE_INFO_ONLY(camera, ListEntitiesCameraResponse)
#endif
#ifdef USE_INFRARED
CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse)
#endif
#ifdef USE_EVENT
CASE_INFO_ONLY(event, ListEntitiesEventResponse)
#endif
#ifdef USE_UPDATE
CASE_STATE_INFO(update, UpdateStateResponse, ListEntitiesUpdateResponse)
#endif
// Special messages (not entity state/info)
case ListEntitiesDoneResponse::MESSAGE_TYPE:
func = &try_send_list_info_done;
break;
case DisconnectRequest::MESSAGE_TYPE:
func = &try_send_disconnect_request;
break;
case PingRequest::MESSAGE_TYPE:
func = &try_send_ping_request;
break;
default:
return 0;
}
#undef CASE_STATE_INFO
#undef CASE_INFO_ONLY
return func(item.entity, this, remaining_size, is_single);
} }
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,

View File

@@ -12,6 +12,7 @@
#include "esphome/core/string_ref.h" #include "esphome/core/string_ref.h"
#include <functional> #include <functional>
#include <limits>
#include <vector> #include <vector>
namespace esphome::api { namespace esphome::api {
@@ -38,8 +39,8 @@ class APIConnection final : public APIServerConnection {
void loop(); void loop();
bool send_list_info_done() { bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done, return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE,
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); ListEntitiesDoneResponse::ESTIMATED_SIZE);
} }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
@@ -178,7 +179,7 @@ class APIConnection final : public APIServerConnection {
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
void send_event(event::Event *event, StringRef event_type); void send_event(event::Event *event);
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
@@ -540,33 +541,17 @@ class APIConnection final : public APIServerConnection {
// Function pointer type for message encoding // 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, bool is_single);
class MessageCreator {
public:
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
// Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint8_t message_type) const;
private:
union Data {
MessageCreatorPtr function_ptr;
const char *const_char_ptr;
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit
};
// Generic batching mechanism for both state updates and entity info // Generic batching mechanism for both state updates and entity info
struct DeferredBatch { struct DeferredBatch {
struct BatchItem { // Sentinel value for unused aux_data_index
EntityBase *entity; // Entity pointer static constexpr uint8_t AUX_DATA_UNUSED = std::numeric_limits<uint8_t>::max();
MessageCreator creator; // Function that creates the message when needed
uint8_t message_type; // Message type for overhead calculation (max 255)
uint8_t estimated_size; // Estimated message size (max 255 bytes)
// Constructor for creating BatchItem struct BatchItem {
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) EntityBase *entity; // 4 bytes - Entity pointer
: entity(entity), creator(creator), message_type(message_type), estimated_size(estimated_size) {} uint8_t message_type; // 1 byte - Message type for protocol and dispatch
uint8_t estimated_size; // 1 byte - Estimated message size (max 255 bytes)
uint8_t aux_data_index{AUX_DATA_UNUSED}; // 1 byte - For events: index into entity's event_types
// 1 byte padding
}; };
std::vector<BatchItem> items; std::vector<BatchItem> items;
@@ -575,10 +560,11 @@ class APIConnection final : public APIServerConnection {
// No pre-allocation - log connections never use batching, and for // No pre-allocation - log connections never use batching, and for
// connections that do, buffers are released after initial sync anyway // connections that do, buffers are released after initial sync anyway
// Add item to the batch // Add item to the batch (with deduplication)
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = AUX_DATA_UNUSED);
// Add item to the front of the batch (for high priority messages like ping) // Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
// Clear all items // Clear all items
void clear() { void clear() {
@@ -592,6 +578,7 @@ class APIConnection final : public APIServerConnection {
bool empty() const { return items.empty(); } bool empty() const { return items.empty(); }
size_t size() const { return items.size(); } size_t size() const { return items.size(); }
const BatchItem &operator[](size_t index) const { return items[index]; } const BatchItem &operator[](size_t index) const { return items[index]; }
// Release excess capacity - only releases if items already empty // Release excess capacity - only releases if items already empty
void release_buffer() { void release_buffer() {
// Safe to call: batch is processed before release_buffer is called, // Safe to call: batch is processed before release_buffer is called,
@@ -663,17 +650,15 @@ class APIConnection final : public APIServerConnection {
this->flags_.batch_scheduled = false; this->flags_.batch_scheduled = false;
} }
#ifdef HAS_PROTO_MESSAGE_DUMP // Dispatch message encoding based on message_type - replaces function pointer storage
// Helper to log a proto message from a MessageCreator object // Switch assigns pointer, single call site for smaller code size
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) { uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool is_single);
this->flags_.log_only_mode = true;
creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type);
this->flags_.log_only_mode = false;
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void log_batch_item_(const DeferredBatch::BatchItem &item) { void log_batch_item_(const DeferredBatch::BatchItem &item) {
// Use the helper to log the message this->flags_.log_only_mode = true;
this->log_proto_message_(item.entity, item.creator, item.message_type); this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true);
this->flags_.log_only_mode = false;
} }
#endif #endif
@@ -698,63 +683,31 @@ class APIConnection final : public APIServerConnection {
// Helper method to send a message either immediately or via batching // Helper method to send a message either immediately or via batching
// Tries immediate send if should_send_immediately_() returns true and buffer has space // 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 // Falls back to batching if immediate send fails or isn't applicable
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type, bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
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()) { if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
// Now actually encode and send DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) && if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message in verbose mode this->log_batch_item_(item);
this->log_proto_message_(entity, MessageCreator(creator), message_type);
#endif #endif
return true; return true;
} }
// If immediate send failed, fall through to batching
} }
return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
// Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type, estimated_size);
}
// Overload for MessageCreator (used by events which need to capture event_type)
bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
// Try to send immediately if message type should bypass batching and buffer has space
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
// Now actually encode and send
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message in verbose mode
this->log_proto_message_(entity, creator, message_type);
#endif
return true;
}
// If immediate send failed, fall through to batching
}
// Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type, estimated_size);
} }
// Helper function to schedule a deferred message with known message type // Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
this->deferred_batch_.add_item(entity, creator, message_type, estimated_size); uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
this->deferred_batch_.add_item(entity, message_type, estimated_size, aux_data_index);
return this->schedule_batch_(); return this->schedule_batch_();
} }
// Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
}
// Helper function to schedule a high priority message at the front of the batch // Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
uint8_t estimated_size) { this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
return this->schedule_batch_(); return this->schedule_batch_();
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,12 @@ namespace esphome::api {
static const char *const TAG = "api.service"; static const char *const TAG = "api.service";
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void APIServerConnectionBase::log_send_message_(const char *name, const std::string &dump) { void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) {
ESP_LOGVV(TAG, "send_message %s: %s", name, dump.c_str()); ESP_LOGVV(TAG, "send_message %s: %s", name, dump);
}
void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) {
DumpBuffer dump_buf;
ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf));
} }
#endif #endif
@@ -19,7 +23,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
HelloRequest msg; HelloRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_hello_request"), msg);
#endif #endif
this->on_hello_request(msg); this->on_hello_request(msg);
break; break;
@@ -28,7 +32,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DisconnectRequest msg; DisconnectRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_disconnect_request"), msg);
#endif #endif
this->on_disconnect_request(msg); this->on_disconnect_request(msg);
break; break;
@@ -37,7 +41,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DisconnectResponse msg; DisconnectResponse msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_disconnect_response"), msg);
#endif #endif
this->on_disconnect_response(msg); this->on_disconnect_response(msg);
break; break;
@@ -46,7 +50,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
PingRequest msg; PingRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_ping_request"), msg);
#endif #endif
this->on_ping_request(msg); this->on_ping_request(msg);
break; break;
@@ -55,7 +59,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
PingResponse msg; PingResponse msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_ping_response"), msg);
#endif #endif
this->on_ping_response(msg); this->on_ping_response(msg);
break; break;
@@ -64,7 +68,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DeviceInfoRequest msg; DeviceInfoRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_device_info_request"), msg);
#endif #endif
this->on_device_info_request(msg); this->on_device_info_request(msg);
break; break;
@@ -73,7 +77,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ListEntitiesRequest msg; ListEntitiesRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_list_entities_request"), msg);
#endif #endif
this->on_list_entities_request(msg); this->on_list_entities_request(msg);
break; break;
@@ -82,7 +86,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeStatesRequest msg; SubscribeStatesRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_states_request"), msg);
#endif #endif
this->on_subscribe_states_request(msg); this->on_subscribe_states_request(msg);
break; break;
@@ -91,7 +95,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeLogsRequest msg; SubscribeLogsRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_logs_request"), msg);
#endif #endif
this->on_subscribe_logs_request(msg); this->on_subscribe_logs_request(msg);
break; break;
@@ -101,7 +105,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
CoverCommandRequest msg; CoverCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_cover_command_request"), msg);
#endif #endif
this->on_cover_command_request(msg); this->on_cover_command_request(msg);
break; break;
@@ -112,7 +116,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
FanCommandRequest msg; FanCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_fan_command_request"), msg);
#endif #endif
this->on_fan_command_request(msg); this->on_fan_command_request(msg);
break; break;
@@ -123,7 +127,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
LightCommandRequest msg; LightCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_light_command_request"), msg);
#endif #endif
this->on_light_command_request(msg); this->on_light_command_request(msg);
break; break;
@@ -134,7 +138,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SwitchCommandRequest msg; SwitchCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_switch_command_request"), msg);
#endif #endif
this->on_switch_command_request(msg); this->on_switch_command_request(msg);
break; break;
@@ -145,7 +149,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeHomeassistantServicesRequest msg; SubscribeHomeassistantServicesRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"), msg);
#endif #endif
this->on_subscribe_homeassistant_services_request(msg); this->on_subscribe_homeassistant_services_request(msg);
break; break;
@@ -155,7 +159,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
GetTimeResponse msg; GetTimeResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_get_time_response"), msg);
#endif #endif
this->on_get_time_response(msg); this->on_get_time_response(msg);
break; break;
@@ -165,7 +169,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeHomeAssistantStatesRequest msg; SubscribeHomeAssistantStatesRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"), msg);
#endif #endif
this->on_subscribe_home_assistant_states_request(msg); this->on_subscribe_home_assistant_states_request(msg);
break; break;
@@ -176,7 +180,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
HomeAssistantStateResponse msg; HomeAssistantStateResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_home_assistant_state_response"), msg);
#endif #endif
this->on_home_assistant_state_response(msg); this->on_home_assistant_state_response(msg);
break; break;
@@ -187,7 +191,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ExecuteServiceRequest msg; ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_execute_service_request"), msg);
#endif #endif
this->on_execute_service_request(msg); this->on_execute_service_request(msg);
break; break;
@@ -198,7 +202,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
CameraImageRequest msg; CameraImageRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_camera_image_request"), msg);
#endif #endif
this->on_camera_image_request(msg); this->on_camera_image_request(msg);
break; break;
@@ -209,7 +213,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ClimateCommandRequest msg; ClimateCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_climate_command_request"), msg);
#endif #endif
this->on_climate_command_request(msg); this->on_climate_command_request(msg);
break; break;
@@ -220,7 +224,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
NumberCommandRequest msg; NumberCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_number_command_request"), msg);
#endif #endif
this->on_number_command_request(msg); this->on_number_command_request(msg);
break; break;
@@ -231,7 +235,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SelectCommandRequest msg; SelectCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_select_command_request"), msg);
#endif #endif
this->on_select_command_request(msg); this->on_select_command_request(msg);
break; break;
@@ -242,7 +246,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SirenCommandRequest msg; SirenCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_siren_command_request"), msg);
#endif #endif
this->on_siren_command_request(msg); this->on_siren_command_request(msg);
break; break;
@@ -253,7 +257,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
LockCommandRequest msg; LockCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_lock_command_request"), msg);
#endif #endif
this->on_lock_command_request(msg); this->on_lock_command_request(msg);
break; break;
@@ -264,7 +268,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ButtonCommandRequest msg; ButtonCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_button_command_request"), msg);
#endif #endif
this->on_button_command_request(msg); this->on_button_command_request(msg);
break; break;
@@ -275,7 +279,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
MediaPlayerCommandRequest msg; MediaPlayerCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_media_player_command_request"), msg);
#endif #endif
this->on_media_player_command_request(msg); this->on_media_player_command_request(msg);
break; break;
@@ -286,7 +290,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeBluetoothLEAdvertisementsRequest msg; SubscribeBluetoothLEAdvertisementsRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_le_advertisements_request"), msg);
#endif #endif
this->on_subscribe_bluetooth_le_advertisements_request(msg); this->on_subscribe_bluetooth_le_advertisements_request(msg);
break; break;
@@ -297,7 +301,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothDeviceRequest msg; BluetoothDeviceRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_device_request"), msg);
#endif #endif
this->on_bluetooth_device_request(msg); this->on_bluetooth_device_request(msg);
break; break;
@@ -308,7 +312,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTGetServicesRequest msg; BluetoothGATTGetServicesRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_gatt_get_services_request"), msg);
#endif #endif
this->on_bluetooth_gatt_get_services_request(msg); this->on_bluetooth_gatt_get_services_request(msg);
break; break;
@@ -319,7 +323,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTReadRequest msg; BluetoothGATTReadRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_request"), msg);
#endif #endif
this->on_bluetooth_gatt_read_request(msg); this->on_bluetooth_gatt_read_request(msg);
break; break;
@@ -330,7 +334,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTWriteRequest msg; BluetoothGATTWriteRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_request"), msg);
#endif #endif
this->on_bluetooth_gatt_write_request(msg); this->on_bluetooth_gatt_write_request(msg);
break; break;
@@ -341,7 +345,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTReadDescriptorRequest msg; BluetoothGATTReadDescriptorRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_descriptor_request"), msg);
#endif #endif
this->on_bluetooth_gatt_read_descriptor_request(msg); this->on_bluetooth_gatt_read_descriptor_request(msg);
break; break;
@@ -352,7 +356,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTWriteDescriptorRequest msg; BluetoothGATTWriteDescriptorRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_descriptor_request"), msg);
#endif #endif
this->on_bluetooth_gatt_write_descriptor_request(msg); this->on_bluetooth_gatt_write_descriptor_request(msg);
break; break;
@@ -363,7 +367,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTNotifyRequest msg; BluetoothGATTNotifyRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_gatt_notify_request"), msg);
#endif #endif
this->on_bluetooth_gatt_notify_request(msg); this->on_bluetooth_gatt_notify_request(msg);
break; break;
@@ -374,7 +378,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeBluetoothConnectionsFreeRequest msg; SubscribeBluetoothConnectionsFreeRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"), msg);
#endif #endif
this->on_subscribe_bluetooth_connections_free_request(msg); this->on_subscribe_bluetooth_connections_free_request(msg);
break; break;
@@ -385,7 +389,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
UnsubscribeBluetoothLEAdvertisementsRequest msg; UnsubscribeBluetoothLEAdvertisementsRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"), msg);
#endif #endif
this->on_unsubscribe_bluetooth_le_advertisements_request(msg); this->on_unsubscribe_bluetooth_le_advertisements_request(msg);
break; break;
@@ -396,7 +400,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeVoiceAssistantRequest msg; SubscribeVoiceAssistantRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_subscribe_voice_assistant_request"), msg);
#endif #endif
this->on_subscribe_voice_assistant_request(msg); this->on_subscribe_voice_assistant_request(msg);
break; break;
@@ -407,7 +411,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantResponse msg; VoiceAssistantResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_response"), msg);
#endif #endif
this->on_voice_assistant_response(msg); this->on_voice_assistant_response(msg);
break; break;
@@ -418,7 +422,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantEventResponse msg; VoiceAssistantEventResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_event_response"), msg);
#endif #endif
this->on_voice_assistant_event_response(msg); this->on_voice_assistant_event_response(msg);
break; break;
@@ -429,7 +433,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
AlarmControlPanelCommandRequest msg; AlarmControlPanelCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_alarm_control_panel_command_request"), msg);
#endif #endif
this->on_alarm_control_panel_command_request(msg); this->on_alarm_control_panel_command_request(msg);
break; break;
@@ -440,7 +444,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
TextCommandRequest msg; TextCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_text_command_request"), msg);
#endif #endif
this->on_text_command_request(msg); this->on_text_command_request(msg);
break; break;
@@ -451,7 +455,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DateCommandRequest msg; DateCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_date_command_request"), msg);
#endif #endif
this->on_date_command_request(msg); this->on_date_command_request(msg);
break; break;
@@ -462,7 +466,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
TimeCommandRequest msg; TimeCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_time_command_request"), msg);
#endif #endif
this->on_time_command_request(msg); this->on_time_command_request(msg);
break; break;
@@ -473,7 +477,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantAudio msg; VoiceAssistantAudio msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_audio"), msg);
#endif #endif
this->on_voice_assistant_audio(msg); this->on_voice_assistant_audio(msg);
break; break;
@@ -484,7 +488,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ValveCommandRequest msg; ValveCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_valve_command_request"), msg);
#endif #endif
this->on_valve_command_request(msg); this->on_valve_command_request(msg);
break; break;
@@ -495,7 +499,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DateTimeCommandRequest msg; DateTimeCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_date_time_command_request"), msg);
#endif #endif
this->on_date_time_command_request(msg); this->on_date_time_command_request(msg);
break; break;
@@ -506,7 +510,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantTimerEventResponse msg; VoiceAssistantTimerEventResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_timer_event_response"), msg);
#endif #endif
this->on_voice_assistant_timer_event_response(msg); this->on_voice_assistant_timer_event_response(msg);
break; break;
@@ -517,7 +521,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
UpdateCommandRequest msg; UpdateCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_update_command_request"), msg);
#endif #endif
this->on_update_command_request(msg); this->on_update_command_request(msg);
break; break;
@@ -528,7 +532,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantAnnounceRequest msg; VoiceAssistantAnnounceRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_announce_request"), msg);
#endif #endif
this->on_voice_assistant_announce_request(msg); this->on_voice_assistant_announce_request(msg);
break; break;
@@ -539,7 +543,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantConfigurationRequest msg; VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_configuration_request"), msg);
#endif #endif
this->on_voice_assistant_configuration_request(msg); this->on_voice_assistant_configuration_request(msg);
break; break;
@@ -550,7 +554,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantSetConfiguration msg; VoiceAssistantSetConfiguration msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_voice_assistant_set_configuration"), msg);
#endif #endif
this->on_voice_assistant_set_configuration(msg); this->on_voice_assistant_set_configuration(msg);
break; break;
@@ -561,7 +565,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
NoiseEncryptionSetKeyRequest msg; NoiseEncryptionSetKeyRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_noise_encryption_set_key_request"), msg);
#endif #endif
this->on_noise_encryption_set_key_request(msg); this->on_noise_encryption_set_key_request(msg);
break; break;
@@ -572,7 +576,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothScannerSetModeRequest msg; BluetoothScannerSetModeRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_bluetooth_scanner_set_mode_request"), msg);
#endif #endif
this->on_bluetooth_scanner_set_mode_request(msg); this->on_bluetooth_scanner_set_mode_request(msg);
break; break;
@@ -583,7 +587,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ZWaveProxyFrame msg; ZWaveProxyFrame msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_z_wave_proxy_frame"), msg);
#endif #endif
this->on_z_wave_proxy_frame(msg); this->on_z_wave_proxy_frame(msg);
break; break;
@@ -594,7 +598,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ZWaveProxyRequest msg; ZWaveProxyRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_z_wave_proxy_request"), msg);
#endif #endif
this->on_z_wave_proxy_request(msg); this->on_z_wave_proxy_request(msg);
break; break;
@@ -605,7 +609,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
HomeassistantActionResponse msg; HomeassistantActionResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_homeassistant_action_response"), msg);
#endif #endif
this->on_homeassistant_action_response(msg); this->on_homeassistant_action_response(msg);
break; break;
@@ -616,7 +620,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
WaterHeaterCommandRequest msg; WaterHeaterCommandRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_water_heater_command_request"), msg);
#endif #endif
this->on_water_heater_command_request(msg); this->on_water_heater_command_request(msg);
break; break;
@@ -627,7 +631,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
InfraredRFTransmitRawTimingsRequest msg; InfraredRFTransmitRawTimingsRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_infrared_rf_transmit_raw_timings_request: %s", msg.dump().c_str()); this->log_receive_message_(LOG_STR("on_infrared_rf_transmit_raw_timings_request"), msg);
#endif #endif
this->on_infrared_rf_transmit_raw_timings_request(msg); this->on_infrared_rf_transmit_raw_timings_request(msg);
break; break;

View File

@@ -12,14 +12,16 @@ class APIServerConnectionBase : public ProtoService {
public: public:
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
protected: protected:
void log_send_message_(const char *name, const std::string &dump); void log_send_message_(const char *name, const char *dump);
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
public: public:
#endif #endif
bool send_message(const ProtoMessage &msg, uint8_t message_type) { bool send_message(const ProtoMessage &msg, uint8_t message_type) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
this->log_send_message_(msg.message_name(), msg.dump()); DumpBuffer dump_buf;
this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
#endif #endif
return this->send_message_(msg, message_type); return this->send_message_(msg, message_type);
} }

View File

@@ -241,8 +241,10 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \ if (obj->is_internal()) \
return; \ return; \
for (auto &c : this->clients_) \ for (auto &c : this->clients_) { \
c->send_##entity_name##_state(obj); \ if (c->flags_.state_subscription) \
c->send_##entity_name##_state(obj); \
} \
} }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
@@ -318,13 +320,13 @@ API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
// Event is a special case - unlike other entities with simple state fields,
// events store their state in a member accessed via obj->get_last_event_type()
void APIServer::on_event(event::Event *obj) { void APIServer::on_event(event::Event *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_) {
c->send_event(obj, obj->get_last_event_type()); if (c->flags_.state_subscription)
c->send_event(obj);
}
} }
#endif #endif
@@ -333,8 +335,10 @@ void APIServer::on_event(event::Event *obj) {
void APIServer::on_update(update::UpdateEntity *obj) { void APIServer::on_update(update::UpdateEntity *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_) {
c->send_update_state(obj); if (c->flags_.state_subscription)
c->send_update_state(obj);
}
} }
#endif #endif
@@ -554,8 +558,10 @@ bool APIServer::clear_noise_psk(bool make_active) {
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() { void APIServer::request_time() {
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
if (!client->flags_.remove && client->is_authenticated()) if (!client->flags_.remove && client->is_authenticated()) {
client->send_time_request(); client->send_time_request();
return; // Only request from one client to avoid clock conflicts
}
} }
} }
#endif #endif
@@ -615,8 +621,7 @@ void APIServer::on_shutdown() {
if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) { if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) {
// If we can't send the disconnect request directly (tx_buffer full), // If we can't send the disconnect request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority // schedule it at the front of the batch so it will be sent with priority
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE, c->schedule_message_front_(nullptr, DisconnectRequest::MESSAGE_TYPE, DisconnectRequest::ESTIMATED_SIZE);
DisconnectRequest::ESTIMATED_SIZE);
} }
} }
} }
@@ -639,14 +644,6 @@ bool APIServer::teardown() {
#define USE_API_ACTION_CALL_TIMEOUT_MS 30000 // NOLINT #define USE_API_ACTION_CALL_TIMEOUT_MS 30000 // NOLINT
#endif #endif
// SSO-friendly action call key - hex format guarantees max 11 chars ("ac_ffffffff")
// which fits in any std::string SSO buffer (typically 12-15 bytes)
static inline std::string make_action_call_key(uint32_t id) {
char buf[12];
size_t len = snprintf(buf, sizeof(buf), "ac_%x", id);
return std::string(buf, len);
}
uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConnection *conn) { uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConnection *conn) {
uint32_t action_call_id = this->next_action_call_id_++; uint32_t action_call_id = this->next_action_call_id_++;
// Handle wraparound (skip 0 as it means "no call") // Handle wraparound (skip 0 as it means "no call")
@@ -656,7 +653,8 @@ uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConn
this->active_action_calls_.push_back({action_call_id, client_call_id, conn}); this->active_action_calls_.push_back({action_call_id, client_call_id, conn});
// Schedule automatic cleanup after timeout (client will have given up by then) // Schedule automatic cleanup after timeout (client will have given up by then)
this->set_timeout(make_action_call_key(action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS, [this, action_call_id]() { // Uses numeric ID overload to avoid heap allocation from str_sprintf
this->set_timeout(action_call_id, USE_API_ACTION_CALL_TIMEOUT_MS, [this, action_call_id]() {
ESP_LOGD(TAG, "Action call %u timed out", action_call_id); ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
this->unregister_active_action_call(action_call_id); this->unregister_active_action_call(action_call_id);
}); });
@@ -665,8 +663,8 @@ uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConn
} }
void APIServer::unregister_active_action_call(uint32_t action_call_id) { void APIServer::unregister_active_action_call(uint32_t action_call_id) {
// Cancel the timeout for this action call // Cancel the timeout for this action call (uses numeric ID overload)
this->cancel_timeout(make_action_call_key(action_call_id)); this->cancel_timeout(action_call_id);
// Swap-and-pop is more efficient than remove_if for unordered vectors // Swap-and-pop is more efficient than remove_if for unordered vectors
for (size_t i = 0; i < this->active_action_calls_.size(); i++) { for (size_t i = 0; i < this->active_action_calls_.size(); i++) {
@@ -682,8 +680,8 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
// Remove all active action calls for disconnected connection using swap-and-pop // Remove all active action calls for disconnected connection using swap-and-pop
for (size_t i = 0; i < this->active_action_calls_.size();) { for (size_t i = 0; i < this->active_action_calls_.size();) {
if (this->active_action_calls_[i].connection == conn) { if (this->active_action_calls_[i].connection == conn) {
// Cancel the timeout for this action call // Cancel the timeout for this action call (uses numeric ID overload)
this->cancel_timeout(make_action_call_key(this->active_action_calls_[i].action_call_id)); this->cancel_timeout(this->active_action_calls_[i].action_call_id);
std::swap(this->active_action_calls_[i], this->active_action_calls_.back()); std::swap(this->active_action_calls_[i], this->active_action_calls_.back());
this->active_action_calls_.pop_back(); this->active_action_calls_.pop_back();

View File

@@ -9,11 +9,10 @@ namespace esphome::api {
class APIConnection; class APIConnection;
// Macro for generating ListEntitiesIterator handlers // Macro for generating ListEntitiesIterator handlers
// Calls schedule_message_ with try_send_*_info // Calls schedule_message_ which dispatches to try_send_*_info
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ return this->client_->schedule_message_(entity, ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
} }
class ListEntitiesIterator : public ComponentIterator { class ListEntitiesIterator : public ComponentIterator {

View File

@@ -48,14 +48,14 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size
} }
uint32_t field_length = res->as_uint32(); uint32_t field_length = res->as_uint32();
ptr += consumed; ptr += consumed;
if (ptr + field_length > end) { if (field_length > static_cast<size_t>(end - ptr)) {
return count; // Out of bounds return count; // Out of bounds
} }
ptr += field_length; ptr += field_length;
break; break;
} }
case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes
if (ptr + 4 > end) { if (end - ptr < 4) {
return count; return count;
} }
ptr += 4; ptr += 4;
@@ -110,7 +110,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
} }
uint32_t field_length = res->as_uint32(); uint32_t field_length = res->as_uint32();
ptr += consumed; ptr += consumed;
if (ptr + field_length > end) { if (field_length > static_cast<size_t>(end - ptr)) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer)); ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return; return;
} }
@@ -121,7 +121,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
break; break;
} }
case WIRE_TYPE_FIXED32: { // 32-bit case WIRE_TYPE_FIXED32: { // 32-bit
if (ptr + 4 > end) { if (end - ptr < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer)); ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return; return;
} }
@@ -139,12 +139,4 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
} }
} }
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string ProtoMessage::dump() const {
std::string out;
this->dump_to(out);
return out;
}
#endif
} // namespace esphome::api } // namespace esphome::api

View File

@@ -362,6 +362,63 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_; std::vector<uint8_t> *buffer_;
}; };
#ifdef HAS_PROTO_MESSAGE_DUMP
/**
* Fixed-size buffer for message dumps - avoids heap allocation.
* Sized to match the logger's default tx_buffer_size (512 bytes)
* since anything larger gets truncated anyway.
*/
class DumpBuffer {
public:
// Matches default tx_buffer_size in logger component
static constexpr size_t CAPACITY = 512;
DumpBuffer() : pos_(0) { buf_[0] = '\0'; }
DumpBuffer &append(const char *str) {
if (str) {
append_impl_(str, strlen(str));
}
return *this;
}
DumpBuffer &append(const char *str, size_t len) {
append_impl_(str, len);
return *this;
}
DumpBuffer &append(size_t n, char c) {
size_t space = CAPACITY - 1 - pos_;
if (n > space)
n = space;
if (n > 0) {
memset(buf_ + pos_, c, n);
pos_ += n;
buf_[pos_] = '\0';
}
return *this;
}
const char *c_str() const { return buf_; }
size_t size() const { return pos_; }
private:
void append_impl_(const char *str, size_t len) {
size_t space = CAPACITY - 1 - pos_;
if (len > space)
len = space;
if (len > 0) {
memcpy(buf_ + pos_, str, len);
pos_ += len;
buf_[pos_] = '\0';
}
}
char buf_[CAPACITY];
size_t pos_;
};
#endif
class ProtoMessage { class ProtoMessage {
public: public:
virtual ~ProtoMessage() = default; virtual ~ProtoMessage() = default;
@@ -370,8 +427,7 @@ class ProtoMessage {
// Default implementation for messages with no fields // Default implementation for messages with no fields
virtual void calculate_size(ProtoSize &size) const {} virtual void calculate_size(ProtoSize &size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const; virtual const char *dump_to(DumpBuffer &out) const = 0;
virtual void dump_to(std::string &out) const = 0;
virtual const char *message_name() const { return "unknown"; } virtual const char *message_name() const { return "unknown"; }
#endif #endif
}; };

View File

@@ -158,12 +158,14 @@ void ATM90E32Component::setup() {
if (this->enable_offset_calibration_) { if (this->enable_offset_calibration_) {
// Initialize flash storage for offset calibrations // Initialize flash storage for offset calibrations
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_); uint32_t o_hash = fnv1_hash("_offset_calibration_");
o_hash = fnv1_hash_extend(o_hash, this->cs_summary_);
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true); this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
this->restore_offset_calibrations_(); this->restore_offset_calibrations_();
// Initialize flash storage for power offset calibrations // Initialize flash storage for power offset calibrations
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_); uint32_t po_hash = fnv1_hash("_power_offset_calibration_");
po_hash = fnv1_hash_extend(po_hash, this->cs_summary_);
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true); this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
this->restore_power_offset_calibrations_(); this->restore_power_offset_calibrations_();
} else { } else {
@@ -183,7 +185,8 @@ void ATM90E32Component::setup() {
if (this->enable_gain_calibration_) { if (this->enable_gain_calibration_) {
// Initialize flash storage for gain calibration // Initialize flash storage for gain calibration
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_); uint32_t g_hash = fnv1_hash("_gain_calibration_");
g_hash = fnv1_hash_extend(g_hash, this->cs_summary_);
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true); this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
this->restore_gain_calibrations_(); this->restore_gain_calibrations_();

View File

@@ -185,18 +185,16 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
return err; return err;
} }
std::string url_string = str_lower_case(url); if (str_endswith_ignore_case(url, ".wav")) {
if (str_endswith(url_string, ".wav")) {
file_type = AudioFileType::WAV; file_type = AudioFileType::WAV;
} }
#ifdef USE_AUDIO_MP3_SUPPORT #ifdef USE_AUDIO_MP3_SUPPORT
else if (str_endswith(url_string, ".mp3")) { else if (str_endswith_ignore_case(url, ".mp3")) {
file_type = AudioFileType::MP3; file_type = AudioFileType::MP3;
} }
#endif #endif
#ifdef USE_AUDIO_FLAC_SUPPORT #ifdef USE_AUDIO_FLAC_SUPPORT
else if (str_endswith(url_string, ".flac")) { else if (str_endswith_ignore_case(url, ".flac")) {
file_type = AudioFileType::FLAC; file_type = AudioFileType::FLAC;
} }
#endif #endif

View File

@@ -1,8 +1,8 @@
#include "bh1750.h" #include "bh1750.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome { namespace esphome::bh1750 {
namespace bh1750 {
static const char *const TAG = "bh1750.sensor"; static const char *const TAG = "bh1750.sensor";
@@ -13,6 +13,31 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000; static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001; static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
// Measurement time constants (datasheet values)
static constexpr uint16_t MTREG_DEFAULT = 69;
static constexpr uint16_t MTREG_MIN = 31;
static constexpr uint16_t MTREG_MAX = 254;
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
// Conversion constants (datasheet formulas)
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
// MTreg calculation constants
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
static constexpr int COUNTS_NUMERATOR = 10;
static constexpr int COUNTS_DENOMINATOR = 12;
// MTreg register bit manipulation constants
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
/* /*
bh1750 properties: bh1750 properties:
@@ -43,74 +68,7 @@ void BH1750Sensor::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
} this->state_ = IDLE;
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
f(NAN);
return;
}
if (active_mtreg_ != mtreg) {
// set mtreg
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
active_mtreg_ = 0;
f(NAN);
return;
}
active_mtreg_ = mtreg;
}
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = 24 * mtreg / 69;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = 180 * mtreg / 69;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = 180 * mtreg / 69;
break;
default:
f(NAN);
return;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
f(NAN);
return;
}
// probably not needed, but adjust for rounding
meas_time++;
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / mtreg;
if (mode == BH1750_MODE_H2)
lx /= 2.0f;
f(lx);
});
} }
void BH1750Sensor::dump_config() { void BH1750Sensor::dump_config() {
@@ -124,45 +82,189 @@ void BH1750Sensor::dump_config() {
} }
void BH1750Sensor::update() { void BH1750Sensor::update() {
// first do a quick measurement in L-mode with full range const uint32_t now = millis();
// to find right range
this->read_lx_(BH1750_MODE_L, 31, [this](float val) { // Start coarse measurement to determine optimal mode/mtreg
if (std::isnan(val)) { if (this->state_ != IDLE) {
this->status_set_warning(); // Safety timeout: reset if stuck
this->publish_state(NAN); if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Measurement timeout, resetting state");
this->state_ = IDLE;
} else {
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
return; return;
} }
}
BH1750Mode use_mode; if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
uint8_t use_mtreg; this->status_set_warning();
if (val <= 7000) { this->publish_state(NAN);
use_mode = BH1750_MODE_H2; return;
use_mtreg = 254; }
} else {
use_mode = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
this->read_lx_(use_mode, use_mtreg, [this](float val) { this->state_ = WAITING_COARSE_MEASUREMENT;
if (std::isnan(val)) { this->enable_loop(); // Enable loop while measurement in progress
this->status_set_warning(); }
this->publish_state(NAN);
return; void BH1750Sensor::loop() {
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case IDLE:
// Disable loop when idle to save cycles
this->disable_loop();
break;
case WAITING_COARSE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_COARSE_RESULT;
} }
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val); break;
case READING_COARSE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
this->process_coarse_result_(lx);
// Start fine measurement with optimal settings
// fetch millis() again since the read can take a bit
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
this->fail_and_reset_();
break;
}
this->state_ = WAITING_FINE_MEASUREMENT;
break;
}
case WAITING_FINE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_FINE_RESULT;
}
break;
case READING_FINE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
this->status_clear_warning(); this->status_clear_warning();
this->publish_state(val); this->publish_state(lx);
}); this->state_ = IDLE;
}); break;
}
}
}
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
// Power on
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
return false;
}
// Set MTreg if changed
if (this->active_mtreg_ != mtreg) {
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
this->active_mtreg_ = 0;
return false;
}
this->active_mtreg_ = mtreg;
}
// Start measurement
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
default:
return false;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
return false;
}
// Store current measurement parameters
this->current_mode_ = mode;
this->current_mtreg_ = mtreg;
this->measurement_start_time_ = now;
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
return true;
}
bool BH1750Sensor::read_measurement_(float &lx_out) {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
return false;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / RESOLUTION_DIVISOR;
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
if (this->current_mode_ == BH1750_MODE_H2) {
lx /= MODE_H2_DIVISOR;
}
lx_out = lx;
return true;
}
void BH1750Sensor::process_coarse_result_(float lx) {
if (std::isnan(lx)) {
// Use defaults if coarse measurement failed
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
return;
}
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
} else {
this->fine_mode_ = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
}
void BH1750Sensor::fail_and_reset_() {
this->status_set_warning();
this->publish_state(NAN);
this->state_ = IDLE;
} }
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bh1750 } // namespace esphome::bh1750
} // namespace esphome

View File

@@ -4,10 +4,9 @@
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h" #include "esphome/components/i2c/i2c.h"
namespace esphome { namespace esphome::bh1750 {
namespace bh1750 {
enum BH1750Mode { enum BH1750Mode : uint8_t {
BH1750_MODE_L, BH1750_MODE_L,
BH1750_MODE_H, BH1750_MODE_H,
BH1750_MODE_H2, BH1750_MODE_H2,
@@ -21,13 +20,36 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void update() override; void update() override;
void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
protected: protected:
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f); // State machine states
enum State : uint8_t {
IDLE,
WAITING_COARSE_MEASUREMENT,
READING_COARSE_RESULT,
WAITING_FINE_MEASUREMENT,
READING_FINE_RESULT,
};
// 4-byte aligned members
uint32_t measurement_start_time_{0};
uint32_t measurement_duration_{0};
// 1-byte members grouped together to minimize padding
State state_{IDLE};
BH1750Mode current_mode_{BH1750_MODE_L};
uint8_t current_mtreg_{31};
BH1750Mode fine_mode_{BH1750_MODE_H2};
uint8_t fine_mtreg_{254};
uint8_t active_mtreg_{0}; uint8_t active_mtreg_{0};
// Helper methods
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
bool read_measurement_(float &lx_out);
void process_coarse_result_(float lx);
void fail_and_reset_();
}; };
} // namespace bh1750 } // namespace esphome::bh1750
} // namespace esphome

View File

@@ -44,7 +44,7 @@ bool BinarySensor::set_new_state(const optional<bool> &new_state) {
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY) #if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_binary_sensor_update(this); ControllerRegistry::notify_binary_sensor_update(this);
#endif #endif
ESP_LOGD(TAG, "'%s': %s", this->get_name().c_str(), ONOFFMAYBE(new_state)); ESP_LOGD(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
return true; return true;
} }
return false; return false;

View File

@@ -1,9 +1,23 @@
# This file was auto-generated by libretiny/generate_components.py """
# Do not modify its contents. ██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
# For custom pin validators, put validate_pin() or validate_usage() ██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
# in gpio.py file in this directory. ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
# in schema.py file in this directory. ╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
AUTO-GENERATED FILE - DO NOT EDIT!
This file was auto-generated by libretiny/generate_components.py.
Any manual changes WILL BE LOST on regeneration.
To customize this component:
- Pin validators: Create gpio.py with validate_pin() or validate_usage()
- Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
Platform-specific code should be added to the main libretiny component
(__init__.py in esphome/components/libretiny/) rather than here.
"""
from esphome import pins from esphome import pins
from esphome.components import libretiny from esphome.components import libretiny
@@ -27,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent(
board_pins=BK72XX_BOARD_PINS, board_pins=BK72XX_BOARD_PINS,
pin_validation=None, pin_validation=None,
usage_validation=None, usage_validation=None,
supports_atomics=False,
) )

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.logger import request_log_listener
from esphome.components.zephyr import zephyr_add_prj_conf from esphome.components.zephyr import zephyr_add_prj_conf
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE
@@ -25,5 +26,8 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
zephyr_add_prj_conf("BT_NUS", True) zephyr_add_prj_conf("BT_NUS", True)
cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS)) expose_log = config[CONF_TYPE] == CONF_LOGS
cg.add(var.set_expose_log(expose_log))
if expose_log:
request_log_listener() # Request a log listener slot for BLE NUS log streaming
await cg.register_component(var, config) await cg.register_component(var, config)

View File

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

View File

@@ -44,7 +44,7 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase web_server_base.WebServerBase
), ),
cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"), cv.Optional(CONF_COMPRESSION, default="gzip"): cv.one_of("gzip", "br"),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on( cv.only_on(

View File

@@ -81,8 +81,8 @@ void CCS811Component::setup() {
bootloader_version, application_version); bootloader_version, application_version);
if (this->version_ != nullptr) { if (this->version_ != nullptr) {
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15), buf_append_printf(version, sizeof(version), 0, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15),
(application_version >> 4 & 15), application_version); (application_version >> 8 & 15), (application_version >> 4 & 15), application_version);
ESP_LOGD(TAG, "publishing version state: %s", version); ESP_LOGD(TAG, "publishing version state: %s", version);
this->version_->publish_state(version); this->version_->publish_state(version);
} }

View File

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

View File

@@ -436,7 +436,7 @@ void Climate::save_state_() {
} }
void Climate::publish_state() { void Climate::publish_state() {
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
auto traits = this->get_traits(); auto traits = this->get_traits();
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));

View File

@@ -153,7 +153,7 @@ void Cover::publish_state(bool save) {
this->position = clamp(this->position, 0.0f, 1.0f); this->position = clamp(this->position, 0.0f, 1.0f);
this->tilt = clamp(this->tilt, 0.0f, 1.0f); this->tilt = clamp(this->tilt, 0.0f, 1.0f);
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str()); ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
auto traits = this->get_traits(); auto traits = this->get_traits();
if (traits.get_supports_position()) { if (traits.get_supports_position()) {
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f); ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);

View File

@@ -76,7 +76,6 @@ class CS5460AComponent : public Component,
void restart() { restart_(); } void restart() { restart_(); }
void setup() override; void setup() override;
void loop() override {}
void dump_config() override; void dump_config() override;
protected: protected:

View File

@@ -207,20 +207,24 @@ void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{ {
std::string buf = "Parsed:"; // Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
char buf[128];
size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:");
if (have_voltage) { if (have_voltage) {
buf += str_sprintf(" V=%fV", voltage); pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage);
} }
if (have_current) { if (have_current) {
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f); pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f,
calculated_current * 1000.0f);
} }
if (have_power) { if (have_power) {
buf += str_sprintf(" P=%fW", power); pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power);
} }
if (energy != 0.0f) { if (energy != 0.0f) {
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses); buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
} }
ESP_LOGVV(TAG, "%s", buf.c_str()); ESP_LOGVV(TAG, "%s", buf);
} }
#endif #endif
} }

View File

@@ -258,8 +258,9 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
} }
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0}; char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
size_t pos = 0;
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) { for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
sprintf(buf, "%s%02x ", buf, frame[i]); pos = buf_append_printf(buf, sizeof(buf), pos, "%02x ", frame[i]);
} }
ESP_LOGD(TAG, "FRAME %s", buf); ESP_LOGD(TAG, "FRAME %s", buf);
@@ -349,8 +350,9 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) { if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
valid_daikin_frame = true; valid_daikin_frame = true;
size_t bytes_count = data.size() / 2 / 8; size_t bytes_count = data.size() / 2 / 8;
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]); size_t buf_size = bytes_count * 3 + 1;
buf[0] = '\0'; std::unique_ptr<char[]> buf(new char[buf_size]()); // value-initialize (zero-fill)
size_t buf_pos = 0;
for (size_t i = 0; i < bytes_count; i++) { for (size_t i = 0; i < bytes_count; i++) {
uint8_t byte = 0; uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) { for (int8_t bit = 0; bit < 8; bit++) {
@@ -361,19 +363,19 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
break; break;
} }
} }
sprintf(buf.get(), "%s%02x ", buf.get(), byte); buf_pos = buf_append_printf(buf.get(), buf_size, buf_pos, "%02x ", byte);
} }
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size()); ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
} }
if (!valid_daikin_frame) { if (!valid_daikin_frame) {
char sbuf[16 * 10 + 1]; char sbuf[16 * 10 + 1] = {0};
sbuf[0] = '\0'; size_t sbuf_pos = 0;
for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) { for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) {
if ((j - 2) % 16 == 0) { if ((j - 2) % 16 == 0) {
if (j > 0) { if (j > 0) {
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf); ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
} }
sbuf[0] = '\0'; sbuf_pos = 0;
} }
char type_ch = ' '; char type_ch = ' ';
// debug_tolerance = 25% // debug_tolerance = 25%
@@ -401,9 +403,10 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
type_ch = '0'; type_ch = '0';
if (abs(data[j]) > 100000) { if (abs(data[j]) > 100000) {
sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch); sbuf_pos = buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", data[j] > 0 ? 99999 : -99999, type_ch);
} else { } else {
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch); sbuf_pos =
buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", (int) (round(data[j] / 10.) * 10), type_ch);
} }
if (j + 1 == static_cast<size_t>(data.size())) { if (j + 1 == static_cast<size_t>(data.size())) {
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf); ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);

View File

@@ -44,7 +44,7 @@ void DallasTemperatureSensor::update() {
this->send_command_(DALLAS_COMMAND_START_CONVERSION); this->send_command_(DALLAS_COMMAND_START_CONVERSION);
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] { this->set_timeout(this->get_address_name().c_str(), this->millis_to_wait_for_conversion_(), [this] {
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) { if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
this->publish_state(NAN); this->publish_state(NAN);
return; return;

View File

@@ -30,7 +30,7 @@ void DateEntity::publish_state() {
return; return;
} }
this->set_has_state(true); this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); ESP_LOGD(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
this->state_callback_.call(); this->state_callback_.call();
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY) #if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_date_update(this); ControllerRegistry::notify_date_update(this);

View File

@@ -45,8 +45,8 @@ void DateTimeEntity::publish_state() {
return; return;
} }
this->set_has_state(true); this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, ESP_LOGD(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_,
this->month_, this->day_, this->hour_, this->minute_, this->second_); this->day_, this->hour_, this->minute_, this->second_);
this->state_callback_.call(); this->state_callback_.call();
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY) #if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_datetime_update(this); ControllerRegistry::notify_datetime_update(this);

View File

@@ -26,8 +26,7 @@ void TimeEntity::publish_state() {
return; return;
} }
this->set_has_state(true); this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, ESP_LOGD(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_);
this->second_);
this->state_callback_.call(); this->state_callback_.call();
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY) #if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_time_update(this); ControllerRegistry::notify_time_update(this);

View File

@@ -30,7 +30,7 @@ void DebugComponent::dump_config() {
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE]; char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION); size_t pos = buf_append_printf(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
this->free_heap_ = get_free_heap_(); this->free_heap_ = get_free_heap_();
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);

View File

@@ -5,12 +5,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/macros.h" #include "esphome/core/macros.h"
#include <span> #include <span>
#include <cstdarg>
#include <cstdio>
#include <algorithm>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
@@ -25,40 +19,7 @@ namespace debug {
static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256; static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256;
static constexpr size_t RESET_REASON_BUFFER_SIZE = 128; static constexpr size_t RESET_REASON_BUFFER_SIZE = 128;
#ifdef USE_ESP8266 // buf_append_printf is now provided by esphome/core/helpers.h
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
// Format strings must be wrapped with PSTR() macro
inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) {
if (pos >= size) {
return size;
}
va_list args;
va_start(args, fmt);
int written = vsnprintf_P(buf + pos, size - pos, fmt, args);
va_end(args);
if (written < 0) {
return pos; // encoding error
}
return std::min(pos + static_cast<size_t>(written), size);
}
#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
#else
/// Safely append formatted string to buffer, returning new position (capped at size)
__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt,
...) {
if (pos >= size) {
return size;
}
va_list args;
va_start(args, fmt);
int written = vsnprintf(buf + pos, size - pos, fmt, args);
va_end(args);
if (written < 0) {
return pos; // encoding error
}
return std::min(pos + static_cast<size_t>(written), size);
}
#endif
class DebugComponent : public PollingComponent { class DebugComponent : public PollingComponent {
public: public:
@@ -74,8 +35,11 @@ class DebugComponent : public PollingComponent {
#ifdef USE_SENSOR #ifdef USE_SENSOR
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
void set_min_free_sensor(sensor::Sensor *min_free_sensor) { min_free_sensor_ = min_free_sensor; }
#endif #endif
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -97,8 +61,11 @@ class DebugComponent : public PollingComponent {
sensor::Sensor *free_sensor_{nullptr}; sensor::Sensor *free_sensor_{nullptr};
sensor::Sensor *block_sensor_{nullptr}; sensor::Sensor *block_sensor_{nullptr};
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
sensor::Sensor *fragmentation_sensor_{nullptr}; sensor::Sensor *fragmentation_sensor_{nullptr};
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
sensor::Sensor *min_free_sensor_{nullptr};
#endif #endif
sensor::Sensor *loop_time_sensor_{nullptr}; sensor::Sensor *loop_time_sensor_{nullptr};
#ifdef USE_ESP32 #ifdef USE_ESP32

View File

@@ -173,8 +173,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
flash_mode); flash_mode);
#endif #endif
esp_chip_info_t info; esp_chip_info_t info;
@@ -182,60 +182,71 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
const char *model = ESPHOME_VARIANT; const char *model = ESPHOME_VARIANT;
// Build features string // Build features string
pos = buf_append(buf, size, pos, "|Chip: %s Features:", model); pos = buf_append_printf(buf, size, pos, "|Chip: %s Features:", model);
bool first_feature = true; bool first_feature = true;
for (const auto &feature : CHIP_FEATURES) { for (const auto &feature : CHIP_FEATURES) {
if (info.features & feature.bit) { if (info.features & feature.bit) {
pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name); pos = buf_append_printf(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
first_feature = false; first_feature = false;
info.features &= ~feature.bit; info.features &= ~feature.bit;
} }
} }
if (info.features != 0) { if (info.features != 0) {
pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features); pos = buf_append_printf(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
} }
ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision); ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision);
pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision); pos = buf_append_printf(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000; uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
// Framework detection // Framework detection
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
ESP_LOGD(TAG, "Framework: Arduino"); ESP_LOGD(TAG, "Framework: Arduino");
pos = buf_append(buf, size, pos, "|Framework: Arduino"); pos = buf_append_printf(buf, size, pos, "|Framework: Arduino");
#elif defined(USE_ESP32) #elif defined(USE_ESP32)
ESP_LOGD(TAG, "Framework: ESP-IDF"); ESP_LOGD(TAG, "Framework: ESP-IDF");
pos = buf_append(buf, size, pos, "|Framework: ESP-IDF"); pos = buf_append_printf(buf, size, pos, "|Framework: ESP-IDF");
#else #else
ESP_LOGW(TAG, "Framework: UNKNOWN"); ESP_LOGW(TAG, "Framework: UNKNOWN");
pos = buf_append(buf, size, pos, "|Framework: UNKNOWN"); pos = buf_append_printf(buf, size, pos, "|Framework: UNKNOWN");
#endif #endif
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version()); pos = buf_append_printf(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
uint8_t mac[6]; uint8_t mac[6];
get_mac_address_raw(mac); get_mac_address_raw(mac);
ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], pos = buf_append_printf(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3],
mac[5]); mac[4], mac[5]);
char reason_buffer[RESET_REASON_BUFFER_SIZE]; char reason_buffer[RESET_REASON_BUFFER_SIZE];
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer)); const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer)); const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause); pos = buf_append_printf(buf, size, pos, "|Wakeup: %s", wakeup_cause);
return pos; return pos;
} }
void DebugComponent::update_platform_() { void DebugComponent::update_platform_() {
#ifdef USE_SENSOR #ifdef USE_SENSOR
uint32_t max_alloc = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
if (this->block_sensor_ != nullptr) { if (this->block_sensor_ != nullptr) {
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); this->block_sensor_->publish_state(max_alloc);
}
if (this->min_free_sensor_ != nullptr) {
this->min_free_sensor_->publish_state(heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
}
if (this->fragmentation_sensor_ != nullptr) {
uint32_t free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
if (free_heap > 0) {
float fragmentation = 100.0f - (100.0f * max_alloc / free_heap);
this->fragmentation_sensor_->publish_state(fragmentation);
}
} }
if (this->psram_sensor_ != nullptr) { if (this->psram_sensor_ != nullptr) {
this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));

View File

@@ -53,8 +53,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
flash_mode); flash_mode);
#if !defined(CLANG_TIDY) #if !defined(CLANG_TIDY)
char reason_buffer[RESET_REASON_BUFFER_SIZE]; char reason_buffer[RESET_REASON_BUFFER_SIZE];
@@ -77,15 +77,15 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id, chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id,
reset_reason, ESP.getResetInfo().c_str()); reset_reason, ESP.getResetInfo().c_str());
pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id); pos = buf_append_printf(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion()); pos = buf_append_printf(buf, size, pos, "|SDK: %s", ESP.getSdkVersion());
pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str()); pos = buf_append_printf(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str());
pos = buf_append(buf, size, pos, "|Boot: %u", boot_version); pos = buf_append_printf(buf, size, pos, "|Boot: %u", boot_version);
pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode); pos = buf_append_printf(buf, size, pos, "|Mode: %u", boot_mode);
pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq); pos = buf_append_printf(buf, size, pos, "|CPU: %u", cpu_freq);
pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id); pos = buf_append_printf(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str()); pos = buf_append_printf(buf, size, pos, "|%s", ESP.getResetInfo().c_str());
#endif #endif
return pos; return pos;

View File

@@ -36,12 +36,12 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id, lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
lt_get_board_code(), flash_kib, ram_kib, reset_reason); lt_get_board_code(), flash_kib, ram_kib, reset_reason);
pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10); pos = buf_append_printf(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason); pos = buf_append_printf(buf, size, pos, "|Reset Reason: %s", reset_reason);
pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name()); pos = buf_append_printf(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id); pos = buf_append_printf(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib); pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib); pos = buf_append_printf(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
return pos; return pos;
} }
@@ -51,6 +51,9 @@ void DebugComponent::update_platform_() {
if (this->block_sensor_ != nullptr) { if (this->block_sensor_ != nullptr) {
this->block_sensor_->publish_state(lt_heap_get_max_alloc()); this->block_sensor_->publish_state(lt_heap_get_max_alloc());
} }
if (this->min_free_sensor_ != nullptr) {
this->min_free_sensor_->publish_state(lt_heap_get_min_free());
}
#endif #endif
} }

View File

@@ -19,7 +19,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
uint32_t cpu_freq = rp2040.f_cpu(); uint32_t cpu_freq = rp2040.f_cpu();
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq); ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq); pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
return pos; return pos;
} }

View File

@@ -20,9 +20,9 @@ static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set,
return pos; return pos;
} }
if (pos > 0) { if (pos > 0) {
pos = buf_append(buf, size, pos, ", "); pos = buf_append_printf(buf, size, pos, ", ");
} }
return buf_append(buf, size, pos, "%s", reason); return buf_append_printf(buf, size, pos, "%s", reason);
} }
static inline uint32_t read_mem_u32(uintptr_t addr) { static inline uint32_t read_mem_u32(uintptr_t addr) {
@@ -140,7 +140,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
const char *supply_status = const char *supply_status =
(nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage."; (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage.";
ESP_LOGD(TAG, "Main supply status: %s", supply_status); ESP_LOGD(TAG, "Main supply status: %s", supply_status);
pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status); pos = buf_append_printf(buf, size, pos, "|Main supply status: %s", supply_status);
// Regulator stage 0 // Regulator stage 0
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) { if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
@@ -172,16 +172,16 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
reg0_voltage = "???V"; reg0_voltage = "???V";
} }
ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage); ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage); pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
} else { } else {
ESP_LOGD(TAG, "Regulator stage 0: disabled"); ESP_LOGD(TAG, "Regulator stage 0: disabled");
pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled"); pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: disabled");
} }
// Regulator stage 1 // Regulator stage 1
const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO"; const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type); ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type);
pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type); pos = buf_append_printf(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
// USB power state // USB power state
const char *usb_state; const char *usb_state;
@@ -195,7 +195,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
usb_state = "disconnected"; usb_state = "disconnected";
} }
ESP_LOGD(TAG, "USB power state: %s", usb_state); ESP_LOGD(TAG, "USB power state: %s", usb_state);
pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state); pos = buf_append_printf(buf, size, pos, "|USB power state: %s", usb_state);
// Power-fail comparator // Power-fail comparator
bool enabled; bool enabled;
@@ -300,14 +300,14 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
break; break;
} }
ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
} else { } else {
ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage); ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage);
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage); pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
} }
} else { } else {
ESP_LOGD(TAG, "Power-fail comparator: disabled"); ESP_LOGD(TAG, "Power-fail comparator: disabled");
pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled"); pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: disabled");
} }
auto package = [](uint32_t value) { auto package = [](uint32_t value) {

View File

@@ -11,16 +11,24 @@ from esphome.const import (
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
ICON_COUNTER, ICON_COUNTER,
ICON_TIMER, ICON_TIMER,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
UNIT_BYTES, UNIT_BYTES,
UNIT_HERTZ, UNIT_HERTZ,
UNIT_MILLISECOND, UNIT_MILLISECOND,
UNIT_PERCENT, UNIT_PERCENT,
) )
from . import CONF_DEBUG_ID, DebugComponent from . import ( # noqa: F401 pylint: disable=unused-import
CONF_DEBUG_ID,
FILTER_SOURCE_FILES,
DebugComponent,
)
DEPENDENCIES = ["debug"] DEPENDENCIES = ["debug"]
CONF_MIN_FREE = "min_free"
CONF_PSRAM = "psram" CONF_PSRAM = "psram"
CONFIG_SCHEMA = { CONFIG_SCHEMA = {
@@ -38,8 +46,14 @@ CONFIG_SCHEMA = {
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_FRAGMENTATION): cv.All( cv.Optional(CONF_FRAGMENTATION): cv.All(
cv.only_on_esp8266, cv.Any(
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), cv.All(
cv.only_on_esp8266,
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
),
cv.only_on_esp32,
msg="This feature is only available on ESP8266 (Arduino 2.5.2+) and ESP32",
),
sensor.sensor_schema( sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
icon=ICON_COUNTER, icon=ICON_COUNTER,
@@ -47,6 +61,19 @@ CONFIG_SCHEMA = {
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
), ),
cv.Optional(CONF_MIN_FREE): cv.All(
cv.Any(
cv.only_on_esp32,
cv.only_on([PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX]),
msg="This feature is only available on ESP32 and LibreTiny (BK72xx, LN882x, RTL87xx)",
),
sensor.sensor_schema(
unit_of_measurement=UNIT_BYTES,
icon=ICON_COUNTER,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
),
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLISECOND, unit_of_measurement=UNIT_MILLISECOND,
icon=ICON_TIMER, icon=ICON_TIMER,
@@ -89,6 +116,10 @@ async def to_code(config):
sens = await sensor.new_sensor(fragmentation_conf) sens = await sensor.new_sensor(fragmentation_conf)
cg.add(debug_component.set_fragmentation_sensor(sens)) cg.add(debug_component.set_fragmentation_sensor(sens))
if min_free_conf := config.get(CONF_MIN_FREE):
sens = await sensor.new_sensor(min_free_conf)
cg.add(debug_component.set_min_free_sensor(sens))
if loop_time_conf := config.get(CONF_LOOP_TIME): if loop_time_conf := config.get(CONF_LOOP_TIME):
sens = await sensor.new_sensor(loop_time_conf) sens = await sensor.new_sensor(loop_time_conf)
cg.add(debug_component.set_loop_time_sensor(sens)) cg.add(debug_component.set_loop_time_sensor(sens))

View File

@@ -8,7 +8,11 @@ from esphome.const import (
ICON_RESTART, ICON_RESTART,
) )
from . import CONF_DEBUG_ID, DebugComponent from . import ( # noqa: F401 pylint: disable=unused-import
CONF_DEBUG_ID,
FILTER_SOURCE_FILES,
DebugComponent,
)
DEPENDENCIES = ["debug"] DEPENDENCIES = ["debug"]

View File

@@ -127,7 +127,9 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
this->max4_ = max4 = -1; this->max4_ = max4 = -1;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15); char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
this->cmd_ = buf;
} else if (min3 < 0 || max3 < 0) { } else if (min3 < 0 || max3 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15; this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15; this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -135,7 +137,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->max2_ = max2 = round(max2 / 0.15) * 0.15; this->max2_ = max2 = round(max2 / 0.15) * 0.15;
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1; this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15); char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
max2 / 0.15);
this->cmd_ = buf;
} else if (min4 < 0 || max4 < 0) { } else if (min4 < 0 || max4 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15; this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15; this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -145,9 +150,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->max3_ = max3 = round(max3 / 0.15) * 0.15; this->max3_ = max3 = round(max3 / 0.15) * 0.15;
this->min4_ = min4 = this->max4_ = max4 = -1; this->min4_ = min4 = this->max4_ = max4 = -1;
this->cmd_ = str_sprintf("detRangeCfg -1 " char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
"%.0f %.0f %.0f %.0f %.0f %.0f", snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15); max2 / 0.15, min3 / 0.15, max3 / 0.15);
this->cmd_ = buf;
} else { } else {
this->min1_ = min1 = round(min1 / 0.15) * 0.15; this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15; this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -158,10 +164,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->min4_ = min4 = round(min4 / 0.15) * 0.15; this->min4_ = min4 = round(min4 / 0.15) * 0.15;
this->max4_ = max4 = round(max4 / 0.15) * 0.15; this->max4_ = max4 = round(max4 / 0.15) * 0.15;
this->cmd_ = str_sprintf("detRangeCfg -1 " char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
max4 / 0.15); this->cmd_ = buf;
} }
this->min1_ = min1; this->min1_ = min1;
@@ -203,7 +209,10 @@ SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_af
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f; delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f); this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f); this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_); // max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
char buf[32];
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
this->cmd_ = buf;
}; };
uint8_t SetLatencyCommand::on_message(std::string &message) { uint8_t SetLatencyCommand::on_message(std::string &message) {

View File

@@ -75,8 +75,8 @@ class SetLatencyCommand : public Command {
class SensorCfgStartCommand : public Command { class SensorCfgStartCommand : public Command {
public: public:
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) { SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
char tmp_cmd[20] = {0}; char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode); buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
cmd_ = std::string(tmp_cmd); cmd_ = std::string(tmp_cmd);
} }
uint8_t on_message(std::string &message) override; uint8_t on_message(std::string &message) override;
@@ -142,8 +142,8 @@ class SensitivityCommand : public Command {
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) { SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
if (sensitivity > 9) if (sensitivity > 9)
sensitivity_ = sensitivity = 9; sensitivity_ = sensitivity = 9;
char tmp_cmd[20] = {0}; char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17
sprintf(tmp_cmd, "setSensitivity %d", sensitivity); buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
cmd_ = std::string(tmp_cmd); cmd_ = std::string(tmp_cmd);
}; };
uint8_t on_message(std::string &message) override; uint8_t on_message(std::string &message) override;

View File

@@ -25,29 +25,13 @@ dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice) Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice)
def _validate_key(value):
value = cv.string_strict(value)
parts = [value[i : i + 2] for i in range(0, len(value), 2)]
if len(parts) != 16:
raise cv.Invalid("Decryption key must consist of 16 hexadecimal numbers")
parts_int = []
if any(len(part) != 2 for part in parts):
raise cv.Invalid("Decryption key must be format XX")
for part in parts:
try:
parts_int.append(int(part, 16))
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid("Decryption key must be hex values from 00 to FF")
return "".join(f"{part:02X}" for part in parts_int)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(Dsmr), cv.GenerateID(): cv.declare_id(Dsmr),
cv.Optional(CONF_DECRYPTION_KEY): _validate_key, cv.Optional(CONF_DECRYPTION_KEY): lambda value: cv.bind_key(
value, name="Decryption key"
),
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_, cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_,

View File

@@ -1,4 +1,5 @@
#include "dsmr.h" #include "dsmr.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <AES.h> #include <AES.h>
@@ -294,8 +295,8 @@ void Dsmr::dump_config() {
DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, ) DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
} }
void Dsmr::set_decryption_key(const std::string &decryption_key) { void Dsmr::set_decryption_key(const char *decryption_key) {
if (decryption_key.empty()) { if (decryption_key == nullptr || decryption_key[0] == '\0') {
ESP_LOGI(TAG, "Disabling decryption"); ESP_LOGI(TAG, "Disabling decryption");
this->decryption_key_.clear(); this->decryption_key_.clear();
if (this->crypt_telegram_ != nullptr) { if (this->crypt_telegram_ != nullptr) {
@@ -305,21 +306,15 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
return; return;
} }
if (decryption_key.length() != 32) { if (!parse_hex(decryption_key, this->decryption_key_, 16)) {
ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); ESP_LOGE(TAG, "Error, decryption key must be 32 hex characters");
this->decryption_key_.clear();
return; return;
} }
this->decryption_key_.clear();
ESP_LOGI(TAG, "Decryption key is set"); ESP_LOGI(TAG, "Decryption key is set");
// Verbose level prints decryption key // Verbose level prints decryption key
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); ESP_LOGV(TAG, "Using decryption key: %s", decryption_key);
char temp[3] = {0};
for (int i = 0; i < 16; i++) {
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
}
if (this->crypt_telegram_ == nullptr) { if (this->crypt_telegram_ == nullptr) {
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT

View File

@@ -63,7 +63,7 @@ class Dsmr : public Component, public uart::UARTDevice {
void dump_config() override; void dump_config() override;
void set_decryption_key(const std::string &decryption_key); void set_decryption_key(const char *decryption_key);
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; } void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; } void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; } void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }

View File

@@ -184,6 +184,7 @@ async def to_code(config):
height, height,
init_sequence_id, init_sequence_id,
init_sequence_length, init_sequence_length,
*model.get_constructor_args(config),
) )
# Rotation is handled by setting the transform # Rotation is handled by setting the transform

View File

@@ -54,20 +54,14 @@ void EPaperBase::setup_pins_() const {
float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
void EPaperBase::command(uint8_t value) { void EPaperBase::command(uint8_t value) {
this->start_command_(); ESP_LOGV(TAG, "Command: 0x%02X", value);
this->dc_pin_->digital_write(false);
this->enable();
this->write_byte(value); this->write_byte(value);
this->end_command_(); this->disable();
}
void EPaperBase::data(uint8_t value) {
this->start_data_();
this->write_byte(value);
this->end_data_();
} }
// write a command followed by zero or more bytes of data. // write a command followed by zero or more bytes of data.
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) { void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)]; char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
@@ -130,14 +124,10 @@ void EPaperBase::wait_for_idle_(bool should_wait) {
void EPaperBase::loop() { void EPaperBase::loop() {
auto now = millis(); auto now = millis();
if (this->delay_until_ != 0) { // using modulus arithmetic to handle wrap-around
// using modulus arithmetic to handle wrap-around int diff = now - this->delay_until_;
int diff = now - this->delay_until_; if (diff < 0)
if (diff < 0) { return;
return;
}
this->delay_until_ = 0;
}
if (this->waiting_for_idle_) { if (this->waiting_for_idle_) {
if (this->is_idle_()) { if (this->is_idle_()) {
this->waiting_for_idle_ = false; this->waiting_for_idle_ = false;
@@ -192,7 +182,7 @@ void EPaperBase::process_state_() {
this->set_state_(EPaperState::RESET); this->set_state_(EPaperState::RESET);
break; break;
case EPaperState::INITIALISE: case EPaperState::INITIALISE:
this->initialise_(); this->initialise(this->update_count_ != 0);
this->set_state_(EPaperState::TRANSFER_DATA); this->set_state_(EPaperState::TRANSFER_DATA);
break; break;
case EPaperState::TRANSFER_DATA: case EPaperState::TRANSFER_DATA:
@@ -230,11 +220,11 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_()); ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
this->state_ = state; this->state_ = state;
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT); this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
if (delay != 0) { // allow subclasses to nominate delays
this->delay_until_ = millis() + delay; if (delay == 0)
} else { delay = this->next_delay_;
this->delay_until_ = 0; this->next_delay_ = 0;
} this->delay_until_ = millis() + delay;
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay, ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
TRUEFALSE(this->waiting_for_idle_)); TRUEFALSE(this->waiting_for_idle_));
if (state == EPaperState::IDLE) { if (state == EPaperState::IDLE) {
@@ -242,22 +232,14 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
} }
} }
void EPaperBase::start_command_() {
this->dc_pin_->digital_write(false);
this->enable();
}
void EPaperBase::end_command_() { this->disable(); }
void EPaperBase::start_data_() { void EPaperBase::start_data_() {
this->dc_pin_->digital_write(true); this->dc_pin_->digital_write(true);
this->enable(); this->enable();
} }
void EPaperBase::end_data_() { this->disable(); }
void EPaperBase::on_safe_shutdown() { this->deep_sleep(); } void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
void EPaperBase::initialise_() { void EPaperBase::initialise(bool partial) {
size_t index = 0; size_t index = 0;
auto *sequence = this->init_sequence_; auto *sequence = this->init_sequence_;
@@ -317,9 +299,8 @@ bool EPaperBase::rotate_coordinates_(int &x, int &y) {
void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
if (!rotate_coordinates_(x, y)) if (!rotate_coordinates_(x, y))
return; return;
const size_t pixel_position = y * this->width_ + x; const size_t byte_position = y * this->row_width_ + x / 8;
const size_t byte_position = pixel_position / 8; const uint8_t bit_position = x % 8;
const uint8_t bit_position = pixel_position % 8;
const uint8_t pixel_bit = 0x80 >> bit_position; const uint8_t pixel_bit = 0x80 >> bit_position;
const auto original = this->buffer_[byte_position]; const auto original = this->buffer_[byte_position];
if ((color_to_bit(color) == 0)) { if ((color_to_bit(color) == 0)) {

View File

@@ -36,14 +36,16 @@ class EPaperBase : public Display,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_2MHZ> { spi::DATA_RATE_2MHZ> {
public: public:
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence = nullptr,
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY) size_t init_sequence_length = 0, DisplayType display_type = DISPLAY_TYPE_BINARY)
: name_(name), : name_(name),
width_(width), width_(width),
height_(height), height_(height),
init_sequence_(init_sequence), init_sequence_(init_sequence),
init_sequence_length_(init_sequence_length), init_sequence_length_(init_sequence_length),
display_type_(display_type) {} display_type_(display_type) {
this->row_width_ = (this->width_ + 7) / 8; // width of a row in bytes
}
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override; float get_setup_priority() const override;
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
@@ -54,9 +56,13 @@ class EPaperBase : public Display,
void dump_config() override; void dump_config() override;
void command(uint8_t value); void command(uint8_t value);
void data(uint8_t value);
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length); void cmd_data(uint8_t command, const uint8_t *ptr, size_t length);
// variant with in-place initializer list
void cmd_data(uint8_t command, std::initializer_list<uint8_t> data) {
this->cmd_data(command, data.begin(), data.size());
}
void update() override; void update() override;
void loop() override; void loop() override;
@@ -109,7 +115,7 @@ class EPaperBase : public Display,
bool is_idle_() const; bool is_idle_() const;
void setup_pins_() const; void setup_pins_() const;
virtual bool reset(); virtual bool reset();
void initialise_(); virtual void initialise(bool partial);
void wait_for_idle_(bool should_wait); void wait_for_idle_(bool should_wait);
bool init_buffer_(size_t buffer_length); bool init_buffer_(size_t buffer_length);
bool rotate_coordinates_(int &x, int &y); bool rotate_coordinates_(int &x, int &y);
@@ -143,14 +149,12 @@ class EPaperBase : public Display,
void set_state_(EPaperState state, uint16_t delay = 0); void set_state_(EPaperState state, uint16_t delay = 0);
void start_command_();
void end_command_();
void start_data_(); void start_data_();
void end_data_();
// properties initialised in the constructor // properties initialised in the constructor
const char *name_; const char *name_;
uint16_t width_; uint16_t width_;
uint16_t row_width_; // width of a row in bytes
uint16_t height_; uint16_t height_;
const uint8_t *init_sequence_; const uint8_t *init_sequence_;
size_t init_sequence_length_; size_t init_sequence_length_;
@@ -163,7 +167,8 @@ class EPaperBase : public Display,
GPIOPin *busy_pin_{}; GPIOPin *busy_pin_{};
GPIOPin *reset_pin_{}; GPIOPin *reset_pin_{};
bool waiting_for_idle_{}; bool waiting_for_idle_{};
uint32_t delay_until_{}; uint32_t delay_until_{}; // timestamp until which to delay processing
uint16_t next_delay_{}; // milliseconds to delay before next state
uint8_t transform_{}; uint8_t transform_{};
uint8_t update_count_{}; uint8_t update_count_{};
// these values represent the bounds of the updated buffer. Note that x_high and y_high // these values represent the bounds of the updated buffer. Note that x_high and y_high

View File

@@ -1,25 +1,24 @@
#include "epaper_spi_ssd1677.h" #include "epaper_spi_mono.h"
#include <algorithm> #include <algorithm>
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome::epaper_spi { namespace esphome::epaper_spi {
static constexpr const char *const TAG = "epaper_spi.ssd1677"; static constexpr const char *const TAG = "epaper_spi.mono";
void EPaperSSD1677::refresh_screen(bool partial) { void EPaperMono::refresh_screen(bool partial) {
ESP_LOGV(TAG, "Refresh screen"); ESP_LOGV(TAG, "Refresh screen");
this->command(0x22); this->cmd_data(0x22, {partial ? (uint8_t) 0xFF : (uint8_t) 0xF7});
this->data(partial ? 0xFF : 0xF7);
this->command(0x20); this->command(0x20);
} }
void EPaperSSD1677::deep_sleep() { void EPaperMono::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep"); ESP_LOGV(TAG, "Deep sleep");
this->command(0x10); this->command(0x10);
} }
bool EPaperSSD1677::reset() { bool EPaperMono::reset() {
if (EPaperBase::reset()) { if (EPaperBase::reset()) {
this->command(0x12); this->command(0x12);
return true; return true;
@@ -27,29 +26,24 @@ bool EPaperSSD1677::reset() {
return false; return false;
} }
bool HOT EPaperSSD1677::transfer_data() { void EPaperMono::set_window() {
// round x-coordinates to byte boundaries
this->x_low_ &= ~7;
this->x_high_ += 7;
this->x_high_ &= ~7;
this->cmd_data(0x44, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256), (uint8_t) (this->x_high_ - 1),
(uint8_t) ((this->x_high_ - 1) / 256)});
this->cmd_data(0x4E, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256)});
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
(uint8_t) ((this->y_high_ - 1) / 256)});
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
}
bool HOT EPaperMono::transfer_data() {
auto start_time = millis(); auto start_time = millis();
if (this->current_data_index_ == 0) { if (this->current_data_index_ == 0) {
uint8_t data[4]{};
// round to byte boundaries // round to byte boundaries
this->x_low_ &= ~7; this->set_window();
this->y_low_ &= ~7;
this->x_high_ += 7;
this->x_high_ &= ~7;
this->y_high_ += 7;
this->y_high_ &= ~7;
data[0] = this->x_low_;
data[1] = this->x_low_ / 256;
data[2] = this->x_high_ - 1;
data[3] = (this->x_high_ - 1) / 256;
cmd_data(0x4E, data, 2);
cmd_data(0x44, data, sizeof(data));
data[0] = this->y_low_;
data[1] = this->y_low_ / 256;
data[2] = this->y_high_ - 1;
data[3] = (this->y_high_ - 1) / 256;
cmd_data(0x4F, data, 2);
this->cmd_data(0x45, data, sizeof(data));
// for monochrome, we still need to clear the red data buffer at least once to prevent it // for monochrome, we still need to clear the red data buffer at least once to prevent it
// causing dirty pixels after partial refresh. // causing dirty pixels after partial refresh.
this->command(this->send_red_ ? 0x26 : 0x24); this->command(this->send_red_ ? 0x26 : 0x24);
@@ -58,10 +52,10 @@ bool HOT EPaperSSD1677::transfer_data() {
size_t row_length = (this->x_high_ - this->x_low_) / 8; size_t row_length = (this->x_high_ - this->x_low_) / 8;
FixedVector<uint8_t> bytes_to_send{}; FixedVector<uint8_t> bytes_to_send{};
bytes_to_send.init(row_length); bytes_to_send.init(row_length);
ESP_LOGV(TAG, "Writing bytes at line %zu at %ums", this->current_data_index_, (unsigned) millis()); ESP_LOGV(TAG, "Writing %u bytes at line %zu at %ums", row_length, this->current_data_index_, (unsigned) millis());
this->start_data_(); this->start_data_();
while (this->current_data_index_ != this->y_high_) { while (this->current_data_index_ != this->y_high_) {
size_t data_idx = (this->current_data_index_ * this->width_ + this->x_low_) / 8; size_t data_idx = this->current_data_index_ * this->row_width_ + this->x_low_ / 8;
for (size_t i = 0; i != row_length; i++) { for (size_t i = 0; i != row_length; i++) {
bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++]; bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++];
} }
@@ -69,12 +63,12 @@ bool HOT EPaperSSD1677::transfer_data() {
this->write_array(&bytes_to_send.front(), row_length); // NOLINT this->write_array(&bytes_to_send.front(), row_length); // NOLINT
if (millis() - start_time > MAX_TRANSFER_TIME) { if (millis() - start_time > MAX_TRANSFER_TIME) {
// Let the main loop run and come back next loop // Let the main loop run and come back next loop
this->end_data_(); this->disable();
return false; return false;
} }
} }
this->end_data_(); this->disable();
this->current_data_index_ = 0; this->current_data_index_ = 0;
if (this->send_red_) { if (this->send_red_) {
this->send_red_ = false; this->send_red_ = false;

View File

@@ -3,13 +3,15 @@
#include "epaper_spi.h" #include "epaper_spi.h"
namespace esphome::epaper_spi { namespace esphome::epaper_spi {
/**
class EPaperSSD1677 : public EPaperBase { * A class for monochrome epaper displays.
*/
class EPaperMono : public EPaperBase {
public: public:
EPaperSSD1677(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, EPaperMono(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length) size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) { : EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
this->buffer_length_ = width * height / 8; // 8 pixels per byte this->buffer_length_ = (width + 7) / 8 * height; // 8 pixels per byte, rounded up
} }
protected: protected:
@@ -18,6 +20,7 @@ class EPaperSSD1677 : public EPaperBase {
void power_off() override{}; void power_off() override{};
void deep_sleep() override; void deep_sleep() override;
bool reset() override; bool reset() override;
virtual void set_window();
bool transfer_data() override; bool transfer_data() override;
bool send_red_{true}; bool send_red_{true};
}; };

View File

@@ -80,20 +80,17 @@ void EPaperSpectraE6::power_on() {
void EPaperSpectraE6::power_off() { void EPaperSpectraE6::power_off() {
ESP_LOGV(TAG, "Power off"); ESP_LOGV(TAG, "Power off");
this->command(0x02); this->cmd_data(0x02, {0x00});
this->data(0x00);
} }
void EPaperSpectraE6::refresh_screen(bool partial) { void EPaperSpectraE6::refresh_screen(bool partial) {
ESP_LOGV(TAG, "Refresh"); ESP_LOGV(TAG, "Refresh");
this->command(0x12); this->cmd_data(0x12, {0x00});
this->data(0x00);
} }
void EPaperSpectraE6::deep_sleep() { void EPaperSpectraE6::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep"); ESP_LOGV(TAG, "Deep sleep");
this->command(0x07); this->cmd_data(0x07, {0xA5});
this->data(0xA5);
} }
void EPaperSpectraE6::fill(Color color) { void EPaperSpectraE6::fill(Color color) {
@@ -143,7 +140,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
if (buf_idx == sizeof bytes_to_send) { if (buf_idx == sizeof bytes_to_send) {
this->start_data_(); this->start_data_();
this->write_array(bytes_to_send, buf_idx); this->write_array(bytes_to_send, buf_idx);
this->end_data_(); this->disable();
ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis()); ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis());
buf_idx = 0; buf_idx = 0;
@@ -157,7 +154,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
if (buf_idx != 0) { if (buf_idx != 0) {
this->start_data_(); this->start_data_();
this->write_array(bytes_to_send, buf_idx); this->write_array(bytes_to_send, buf_idx);
this->end_data_(); this->disable();
} }
this->current_data_index_ = 0; this->current_data_index_ = 0;
return true; return true;

View File

@@ -0,0 +1,47 @@
#include "epaper_waveshare.h"
namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi.waveshare";
void EpaperWaveshare::initialise(bool partial) {
EPaperBase::initialise(partial);
if (partial) {
this->cmd_data(0x32, this->partial_lut_, this->partial_lut_length_);
this->cmd_data(0x3C, {0x80});
this->cmd_data(0x22, {0xC0});
this->command(0x20);
this->next_delay_ = 100;
} else {
this->cmd_data(0x32, this->lut_, this->lut_length_);
this->cmd_data(0x3C, {0x05});
}
this->send_red_ = true;
}
void EpaperWaveshare::set_window() {
this->x_low_ &= ~7;
this->x_high_ += 7;
this->x_high_ &= ~7;
uint16_t x_start = this->x_low_ / 8;
uint16_t x_end = (this->x_high_ - 1) / 8;
this->cmd_data(0x44, {(uint8_t) x_start, (uint8_t) (x_end)});
this->cmd_data(0x4E, {(uint8_t) x_start});
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
(uint8_t) ((this->y_high_ - 1) / 256)});
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
ESP_LOGV(TAG, "Set window X: %u-%u, Y: %u-%u", this->x_low_, this->x_high_, this->y_low_, this->y_high_);
}
void EpaperWaveshare::refresh_screen(bool partial) {
if (partial) {
this->cmd_data(0x22, {0x0F});
} else {
this->cmd_data(0x22, {0xC7});
}
this->command(0x20);
this->next_delay_ = partial ? 100 : 3000;
}
void EpaperWaveshare::deep_sleep() { this->cmd_data(0x10, {0x01}); }
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,30 @@
#pragma once
#include "epaper_spi.h"
#include "epaper_spi_mono.h"
namespace esphome::epaper_spi {
/**
* An epaper display that needs LUTs to be sent to it.
*/
class EpaperWaveshare : public EPaperMono {
public:
EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut,
uint16_t partial_lut_length)
: EPaperMono(name, width, height, init_sequence, init_sequence_length),
lut_(lut),
lut_length_(lut_length),
partial_lut_(partial_lut),
partial_lut_length_(partial_lut_length) {}
protected:
void initialise(bool partial) override;
void set_window() override;
void refresh_screen(bool partial) override;
void deep_sleep() override;
const uint8_t *lut_;
size_t lut_length_;
const uint8_t *partial_lut_;
uint16_t partial_lut_length_;
};
} // namespace esphome::epaper_spi

View File

@@ -32,6 +32,9 @@ class EpaperModel:
return cv.Required(name) return cv.Required(name)
return cv.Optional(name, default=self.get_default(name, fallback)) return cv.Optional(name, default=self.get_default(name, fallback))
def get_constructor_args(self, config) -> tuple:
return ()
def get_dimensions(self, config) -> tuple[int, int]: def get_dimensions(self, config) -> tuple[int, int]:
if CONF_DIMENSIONS in config: if CONF_DIMENSIONS in config:
# Explicit dimensions, just use as is # Explicit dimensions, just use as is

View File

@@ -4,10 +4,9 @@ from . import EpaperModel
class SSD1677(EpaperModel): class SSD1677(EpaperModel):
def __init__(self, name, class_name="EPaperSSD1677", **kwargs): def __init__(self, name, class_name="EPaperMono", data_rate="20MHz", **defaults):
if CONF_DATA_RATE not in kwargs: defaults[CONF_DATA_RATE] = data_rate
kwargs[CONF_DATA_RATE] = "20MHz" super().__init__(name, class_name, **defaults)
super().__init__(name, class_name, **kwargs)
# fmt: off # fmt: off
def get_init_sequence(self, config: dict): def get_init_sequence(self, config: dict):
@@ -23,11 +22,15 @@ class SSD1677(EpaperModel):
ssd1677 = SSD1677("ssd1677") ssd1677 = SSD1677("ssd1677")
ssd1677.extend( wave_4_26 = ssd1677.extend(
"seeed-ee04-mono-4.26", "waveshare-4.26in",
width=800, width=800,
height=480, height=480,
mirror_x=True, mirror_x=True,
)
wave_4_26.extend(
"seeed-ee04-mono-4.26",
cs_pin=44, cs_pin=44,
dc_pin=10, dc_pin=10,
reset_pin=38, reset_pin=38,

View File

@@ -0,0 +1,88 @@
import esphome.codegen as cg
from esphome.core import ID
from ..display import CONF_INIT_SEQUENCE_ID
from . import EpaperModel
class WaveshareModel(EpaperModel):
def __init__(self, name, lut, lut_partial=None, **defaults):
super().__init__(name, "EpaperWaveshare", **defaults)
self.lut = lut
self.lut_partial = lut_partial
def get_constructor_args(self, config) -> tuple:
lut = (
cg.static_const_array(
ID(config[CONF_INIT_SEQUENCE_ID].id + "_lut", type=cg.uint8), self.lut
),
len(self.lut),
)
if self.lut_partial is None:
lut_partial = cg.nullptr, 0
else:
lut_partial = (
cg.static_const_array(
ID(
config[CONF_INIT_SEQUENCE_ID].id + "_lut_partial", type=cg.uint8
),
self.lut_partial,
),
len(self.lut_partial),
)
return *lut, *lut_partial
# fmt: off
WaveshareModel(
"waveshare-2.13in-v3",
width=122,
height=250,
initsequence=(
(0x01, 0x27, 0x01, 0x00), # driver output control
(0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00),
(0x11, 0x03), # Data entry mode
(0x3F, 0x22), # Undocumented command
(0x2C, 0x36), # write VCOM register
(0x04, 0x41, 0x0C, 0x32), # SRC voltage
(0x03, 0x17), # Gate voltage
(0x21, 0x00, 0x80), # Display update control
(0x18, 0x80), # Select internal temperature sensor
),
lut=(
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0,
0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x0, 0x0, 0x0,
),
lut_partial=(
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x0, 0x0, 0x0,
),
)

View File

@@ -46,6 +46,8 @@ class ESPBTUUID {
esp_bt_uuid_t get_uuid() const; esp_bt_uuid_t get_uuid() const;
// Remove before 2026.8.0
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
std::string to_string() const; std::string to_string() const;
const char *to_str(std::span<char, UUID_STR_LEN> output) const; const char *to_str(std::span<char, UUID_STR_LEN> output) const;

View File

@@ -193,10 +193,18 @@ void BLEClientBase::log_event_(const char *name) {
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name); ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name);
} }
void BLEClientBase::log_gattc_event_(const char *name) { void BLEClientBase::log_gattc_lifecycle_event_(const char *name) {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name); ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
} }
void BLEClientBase::log_gattc_data_event_(const char *name) {
// Data transfer events are logged at VERBOSE level because logging to UART creates
// delays that cause timing issues during time-sensitive BLE operations. This is
// especially problematic during pairing or firmware updates which require rapid
// writes to many characteristics - the log spam can cause these operations to fail.
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
}
void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) { void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status); ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status);
} }
@@ -280,7 +288,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_OPEN_EVT: { case ESP_GATTC_OPEN_EVT: {
if (!this->check_addr(param->open.remote_bda)) if (!this->check_addr(param->open.remote_bda))
return false; return false;
this->log_gattc_event_("OPEN"); this->log_gattc_lifecycle_event_("OPEN");
// conn_id was already set in ESP_GATTC_CONNECT_EVT // conn_id was already set in ESP_GATTC_CONNECT_EVT
this->service_count_ = 0; this->service_count_ = 0;
@@ -331,7 +339,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_CONNECT_EVT: { case ESP_GATTC_CONNECT_EVT: {
if (!this->check_addr(param->connect.remote_bda)) if (!this->check_addr(param->connect.remote_bda))
return false; return false;
this->log_gattc_event_("CONNECT"); this->log_gattc_lifecycle_event_("CONNECT");
this->conn_id_ = param->connect.conn_id; this->conn_id_ = param->connect.conn_id;
// Start MTU negotiation immediately as recommended by ESP-IDF examples // Start MTU negotiation immediately as recommended by ESP-IDF examples
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in // (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
@@ -376,7 +384,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_CLOSE_EVT: { case ESP_GATTC_CLOSE_EVT: {
if (this->conn_id_ != param->close.conn_id) if (this->conn_id_ != param->close.conn_id)
return false; return false;
this->log_gattc_event_("CLOSE"); this->log_gattc_lifecycle_event_("CLOSE");
this->release_services(); this->release_services();
this->set_state(espbt::ClientState::IDLE); this->set_state(espbt::ClientState::IDLE);
this->conn_id_ = UNSET_CONN_ID; this->conn_id_ = UNSET_CONN_ID;
@@ -404,7 +412,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_SEARCH_CMPL_EVT: { case ESP_GATTC_SEARCH_CMPL_EVT: {
if (this->conn_id_ != param->search_cmpl.conn_id) if (this->conn_id_ != param->search_cmpl.conn_id)
return false; return false;
this->log_gattc_event_("SEARCH_CMPL"); this->log_gattc_lifecycle_event_("SEARCH_CMPL");
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery // For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
// This balances performance with bandwidth usage after the critical discovery phase // This balances performance with bandwidth usage after the critical discovery phase
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
@@ -431,35 +439,35 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_READ_DESCR_EVT: { case ESP_GATTC_READ_DESCR_EVT: {
if (this->conn_id_ != param->write.conn_id) if (this->conn_id_ != param->write.conn_id)
return false; return false;
this->log_gattc_event_("READ_DESCR"); this->log_gattc_data_event_("READ_DESCR");
break; break;
} }
case ESP_GATTC_WRITE_DESCR_EVT: { case ESP_GATTC_WRITE_DESCR_EVT: {
if (this->conn_id_ != param->write.conn_id) if (this->conn_id_ != param->write.conn_id)
return false; return false;
this->log_gattc_event_("WRITE_DESCR"); this->log_gattc_data_event_("WRITE_DESCR");
break; break;
} }
case ESP_GATTC_WRITE_CHAR_EVT: { case ESP_GATTC_WRITE_CHAR_EVT: {
if (this->conn_id_ != param->write.conn_id) if (this->conn_id_ != param->write.conn_id)
return false; return false;
this->log_gattc_event_("WRITE_CHAR"); this->log_gattc_data_event_("WRITE_CHAR");
break; break;
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (this->conn_id_ != param->read.conn_id) if (this->conn_id_ != param->read.conn_id)
return false; return false;
this->log_gattc_event_("READ_CHAR"); this->log_gattc_data_event_("READ_CHAR");
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {
if (this->conn_id_ != param->notify.conn_id) if (this->conn_id_ != param->notify.conn_id)
return false; return false;
this->log_gattc_event_("NOTIFY"); this->log_gattc_data_event_("NOTIFY");
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: { case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->log_gattc_event_("REG_FOR_NOTIFY"); this->log_gattc_data_event_("REG_FOR_NOTIFY");
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
// Client is responsible for flipping the descriptor value // Client is responsible for flipping the descriptor value
@@ -491,7 +499,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
esp_err_t status = esp_err_t status =
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en), esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
(uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); (uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties); ESP_LOGV(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
if (status) { if (status) {
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status); this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
} }
@@ -499,13 +507,13 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
} }
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
this->log_gattc_event_("UNREG_FOR_NOTIFY"); this->log_gattc_data_event_("UNREG_FOR_NOTIFY");
break; break;
} }
default: default:
// ideally would check all other events for matching conn_id // Unknown events logged at VERBOSE to avoid UART delays during time-sensitive operations
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event); ESP_LOGV(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
break; break;
} }
return true; return true;

View File

@@ -127,7 +127,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
// 6 bytes used, 2 bytes padding // 6 bytes used, 2 bytes padding
void log_event_(const char *name); void log_event_(const char *name);
void log_gattc_event_(const char *name); void log_gattc_lifecycle_event_(const char *name);
void log_gattc_data_event_(const char *name);
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type); const char *param_type);
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,

View File

@@ -8,6 +8,7 @@
#include <esp_app_desc.h> #include <esp_app_desc.h>
#include <esp_hosted.h> #include <esp_hosted.h>
#include <esp_hosted_host_fw_ver.h> #include <esp_hosted_host_fw_ver.h>
#include <esp_ota_ops.h>
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE #ifdef USE_ESP32_HOSTED_HTTP_UPDATE
#include "esphome/components/json/json_util.h" #include "esphome/components/json/json_util.h"
@@ -68,7 +69,10 @@ void Esp32HostedUpdate::setup() {
// Get coprocessor version // Get coprocessor version
esp_hosted_coprocessor_fwver_t ver_info; esp_hosted_coprocessor_fwver_t ver_info;
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) { if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1); // 16 bytes: "255.255.255" (11 chars) + null + safety margin
char buf[16];
snprintf(buf, sizeof(buf), "%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
this->update_info_.current_version = buf;
} else { } else {
this->update_info_.current_version = "unknown"; this->update_info_.current_version = "unknown";
} }
@@ -293,8 +297,7 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
} }
// Stream firmware to coprocessor while computing SHA256 // Stream firmware to coprocessor while computing SHA256
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+) sha256::SHA256 hasher;
alignas(32) sha256::SHA256 hasher;
hasher.init(); hasher.init();
uint8_t buffer[CHUNK_SIZE]; uint8_t buffer[CHUNK_SIZE];
@@ -351,8 +354,7 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
} }
// Verify SHA256 before writing // Verify SHA256 before writing
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+) sha256::SHA256 hasher;
alignas(32) sha256::SHA256 hasher;
hasher.init(); hasher.init();
hasher.add(this->firmware_data_, this->firmware_size_); hasher.add(this->firmware_data_, this->firmware_size_);
hasher.calculate(); hasher.calculate();
@@ -442,6 +444,12 @@ void Esp32HostedUpdate::perform(bool force) {
this->status_clear_error(); this->status_clear_error();
this->publish_state(); this->publish_state();
#ifdef USE_OTA_ROLLBACK
// Mark the host partition as valid before rebooting, in case the safe mode
// timer hasn't expired yet.
esp_ota_mark_app_valid_cancel_rollback();
#endif
// Schedule a restart to ensure everything is in sync // Schedule a restart to ensure everything is in sync
ESP_LOGI(TAG, "Restarting in 1 second"); ESP_LOGI(TAG, "Restarting in 1 second");
this->set_timeout(1000, []() { App.safe_reboot(); }); this->set_timeout(1000, []() { App.safe_reboot(); });

View File

@@ -6,7 +6,11 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "preferences.h" #include "preferences.h"
#include <Arduino.h> #include <Arduino.h>
#include <Esp.h> #include <core_esp8266_features.h>
extern "C" {
#include <user_interface.h>
}
namespace esphome { namespace esphome {
@@ -16,23 +20,19 @@ void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
uint32_t IRAM_ATTR HOT micros() { return ::micros(); } uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() { void arch_restart() {
ESP.restart(); // NOLINT(readability-static-accessed-through-instance) system_restart();
// restart() doesn't always end execution // restart() doesn't always end execution
while (true) { // NOLINT(clang-diagnostic-unreachable-code) while (true) { // NOLINT(clang-diagnostic-unreachable-code)
yield(); yield();
} }
} }
void arch_init() {} void arch_init() {}
void IRAM_ATTR HOT arch_feed_wdt() { void IRAM_ATTR HOT arch_feed_wdt() { system_soft_wdt_feed(); }
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
}
uint8_t progmem_read_byte(const uint8_t *addr) { uint8_t progmem_read_byte(const uint8_t *addr) {
return pgm_read_byte(addr); // NOLINT return pgm_read_byte(addr); // NOLINT
} }
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
}
uint32_t arch_get_cpu_freq_hz() { return F_CPU; } uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
void force_link_symbols() { void force_link_symbols() {

View File

@@ -99,7 +99,7 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
} }
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const { size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "GPIO%u", this->pin_); return buf_append_printf(buffer, len, 0, "GPIO%u", this->pin_);
} }
bool ESP8266GPIOPin::digital_read() { bool ESP8266GPIOPin::digital_read() {

View File

@@ -370,12 +370,14 @@ void ESPHomeOTAComponent::handle_data_() {
error: error:
this->write_byte_(static_cast<uint8_t>(error_code)); this->write_byte_(static_cast<uint8_t>(error_code));
this->cleanup_connection_();
// Abort backend before cleanup - cleanup_connection_() destroys the backend
if (this->backend_ != nullptr && update_started) { if (this->backend_ != nullptr && update_started) {
this->backend_->abort(); this->backend_->abort();
} }
this->cleanup_connection_();
this->status_momentary_error("err", 5000); this->status_momentary_error("err", 5000);
#ifdef USE_OTA_STATE_LISTENER #ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code)); this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
@@ -561,11 +563,9 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function. // (no passing to other functions). All hash operations must happen in this function.
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for sha256::SHA256 hasher;
// hardware SHA acceleration DMA operations.
alignas(32) sha256::SHA256 hasher;
const size_t hex_size = hasher.get_size() * 2; const size_t hex_size = hasher.get_size() * 2;
const size_t nonce_len = hasher.get_size() / 4; const size_t nonce_len = hasher.get_size() / 4;
@@ -637,11 +637,9 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
const char *cnonce = nonce + hex_size; const char *cnonce = nonce + hex_size;
const char *response = cnonce + hex_size; const char *response = cnonce + hex_size;
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function. // (no passing to other functions). All hash operations must happen in this function.
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for sha256::SHA256 hasher;
// hardware SHA acceleration DMA operations.
alignas(32) sha256::SHA256 hasher;
hasher.init(); hasher.init();
hasher.add(this->password_.c_str(), this->password_.length()); hasher.add(this->password_.c_str(), this->password_.length());

View File

@@ -61,6 +61,21 @@ DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["network"] AUTO_LOAD = ["network"]
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
# Key for tracking IP state listener count in CORE.data
ETHERNET_IP_STATE_LISTENERS_KEY = "ethernet_ip_state_listeners"
def request_ethernet_ip_state_listener() -> None:
"""Request an IP state listener slot.
Components that implement EthernetIPStateListener should call this
in their to_code() to register for IP state notifications.
"""
CORE.data[ETHERNET_IP_STATE_LISTENERS_KEY] = (
CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0) + 1
)
# RMII pins that are hardcoded on ESP32 classic and cannot be changed # RMII pins that are hardcoded on ESP32 classic and cannot be changed
# These pins are used by the internal Ethernet MAC when using RMII PHYs # These pins are used by the internal Ethernet MAC when using RMII PHYs
ESP32_RMII_FIXED_PINS = { ESP32_RMII_FIXED_PINS = {
@@ -411,6 +426,8 @@ async def to_code(config):
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
CORE.add_job(final_step)
def _final_validate_rmii_pins(config: ConfigType) -> None: def _final_validate_rmii_pins(config: ConfigType) -> None:
"""Validate that RMII pins are not used by other components.""" """Validate that RMII pins are not used by other components."""
@@ -467,3 +484,11 @@ def _final_validate(config: ConfigType) -> ConfigType:
FINAL_VALIDATE_SCHEMA = _final_validate FINAL_VALIDATE_SCHEMA = _final_validate
@coroutine_with_priority(CoroPriority.FINAL)
async def final_step():
"""Final code generation step to configure optional Ethernet features."""
if ip_state_count := CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0):
cg.add_define("USE_ETHERNET_IP_STATE_LISTENERS")
cg.add_define("ESPHOME_ETHERNET_IP_STATE_LISTENERS", ip_state_count)

View File

@@ -472,6 +472,12 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
break; break;
case ETHERNET_EVENT_CONNECTED: case ETHERNET_EVENT_CONNECTED:
event_name = "ETH connected"; event_name = "ETH connected";
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP)
if (global_eth_component->manual_ip_.has_value()) {
global_eth_component->notify_ip_state_listeners_();
}
#endif
break; break;
case ETHERNET_EVENT_DISCONNECTED: case ETHERNET_EVENT_DISCONNECTED:
event_name = "ETH disconnected"; event_name = "ETH disconnected";
@@ -498,6 +504,9 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
global_eth_component->connected_ = true; global_eth_component->connected_ = true;
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
#endif /* USE_NETWORK_IPV6 */ #endif /* USE_NETWORK_IPV6 */
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
global_eth_component->notify_ip_state_listeners_();
#endif
} }
#if USE_NETWORK_IPV6 #if USE_NETWORK_IPV6
@@ -514,9 +523,23 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
global_eth_component->connected_ = global_eth_component->got_ipv4_address_; global_eth_component->connected_ = global_eth_component->got_ipv4_address_;
global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
#endif #endif
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
global_eth_component->notify_ip_state_listeners_();
#endif
} }
#endif /* USE_NETWORK_IPV6 */ #endif /* USE_NETWORK_IPV6 */
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void EthernetComponent::notify_ip_state_listeners_() {
auto ips = this->get_ip_addresses();
auto dns1 = this->get_dns_address(0);
auto dns2 = this->get_dns_address(1);
for (auto *listener : this->ip_state_listeners_) {
listener->on_ip_state(ips, dns1, dns2);
}
}
#endif // USE_ETHERNET_IP_STATE_LISTENERS
void EthernetComponent::finish_connect_() { void EthernetComponent::finish_connect_() {
#if USE_NETWORK_IPV6 #if USE_NETWORK_IPV6
// Retry IPv6 link-local setup if it failed during initial connect // Retry IPv6 link-local setup if it failed during initial connect

View File

@@ -17,6 +17,22 @@
namespace esphome { namespace esphome {
namespace ethernet { namespace ethernet {
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
/** Listener interface for Ethernet IP state changes.
*
* Components can implement this interface to receive IP address updates
* without the overhead of std::function callbacks or polling.
*
* @note Components must call ethernet.request_ethernet_ip_state_listener() in their
* Python to_code() to register for this listener type.
*/
class EthernetIPStateListener {
public:
virtual void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
const network::IPAddress &dns2) = 0;
};
#endif // USE_ETHERNET_IP_STATE_LISTENERS
enum EthernetType : uint8_t { enum EthernetType : uint8_t {
ETHERNET_TYPE_UNKNOWN = 0, ETHERNET_TYPE_UNKNOWN = 0,
ETHERNET_TYPE_LAN8720, ETHERNET_TYPE_LAN8720,
@@ -99,12 +115,19 @@ class EthernetComponent : public Component {
eth_speed_t get_link_speed(); eth_speed_t get_link_speed();
bool powerdown(); bool powerdown();
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
#endif
protected: protected:
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
#if LWIP_IPV6 #if LWIP_IPV6
static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
#endif /* LWIP_IPV6 */ #endif /* LWIP_IPV6 */
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void notify_ip_state_listeners_();
#endif
void start_connect_(); void start_connect_();
void finish_connect_(); void finish_connect_();
@@ -163,6 +186,10 @@ class EthernetComponent : public Component {
esp_eth_phy_t *phy_{nullptr}; esp_eth_phy_t *phy_{nullptr};
optional<std::array<uint8_t, 6>> fixed_mac_; optional<std::array<uint8_t, 6>> fixed_mac_;
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
StaticVector<EthernetIPStateListener *, ESPHOME_ETHERNET_IP_STATE_LISTENERS> ip_state_listeners_;
#endif
private: private:
// Stores a pointer to a string literal (static storage duration). // Stores a pointer to a string literal (static storage duration).
// ONLY set from Python-generated code with string literals - never dynamic strings. // ONLY set from Python-generated code with string literals - never dynamic strings.

View File

@@ -7,8 +7,44 @@ namespace esphome::ethernet_info {
static const char *const TAG = "ethernet_info"; static const char *const TAG = "ethernet_info";
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void IPAddressEthernetInfo::setup() { ethernet::global_eth_component->add_ip_state_listener(this); }
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); } void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
void IPAddressEthernetInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
const network::IPAddress &dns2) {
char buf[network::IP_ADDRESS_BUFFER_SIZE];
ips[0].str_to(buf);
this->publish_state(buf);
uint8_t sensor = 0;
for (const auto &ip : ips) {
if (ip.is_set()) {
if (this->ip_sensors_[sensor] != nullptr) {
ip.str_to(buf);
this->ip_sensors_[sensor]->publish_state(buf);
}
sensor++;
}
}
}
void DNSAddressEthernetInfo::setup() { ethernet::global_eth_component->add_ip_state_listener(this); }
void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); } void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); }
void DNSAddressEthernetInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
const network::IPAddress &dns2) {
// IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot
char buf[network::IP_ADDRESS_BUFFER_SIZE * 2];
dns1.str_to(buf);
size_t len1 = strlen(buf);
buf[len1] = ' ';
dns2.str_to(buf + len1 + 1);
this->publish_state(buf);
}
#endif // USE_ETHERNET_IP_STATE_LISTENERS
void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); } void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); }
} // namespace esphome::ethernet_info } // namespace esphome::ethernet_info

View File

@@ -8,64 +8,37 @@
namespace esphome::ethernet_info { namespace esphome::ethernet_info {
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { #ifdef USE_ETHERNET_IP_STATE_LISTENERS
class IPAddressEthernetInfo final : public Component,
public text_sensor::TextSensor,
public ethernet::EthernetIPStateListener {
public: public:
void update() override { void setup() override;
auto ips = ethernet::global_eth_component->get_ip_addresses();
if (ips != this->last_ips_) {
this->last_ips_ = ips;
char buf[network::IP_ADDRESS_BUFFER_SIZE];
ips[0].str_to(buf);
this->publish_state(buf);
uint8_t sensor = 0;
for (auto &ip : ips) {
if (ip.is_set()) {
if (this->ip_sensors_[sensor] != nullptr) {
ip.str_to(buf);
this->ip_sensors_[sensor]->publish_state(buf);
}
sensor++;
}
}
}
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
void dump_config() override; void dump_config() override;
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
// EthernetIPStateListener interface
void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
const network::IPAddress &dns2) override;
protected: protected:
network::IPAddresses last_ips_; std::array<text_sensor::TextSensor *, 5> ip_sensors_{};
std::array<text_sensor::TextSensor *, 5> ip_sensors_;
}; };
class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { class DNSAddressEthernetInfo final : public Component,
public text_sensor::TextSensor,
public ethernet::EthernetIPStateListener {
public: public:
void update() override { void setup() override;
auto dns1 = ethernet::global_eth_component->get_dns_address(0);
auto dns2 = ethernet::global_eth_component->get_dns_address(1);
if (dns1 != this->last_dns1_ || dns2 != this->last_dns2_) {
this->last_dns1_ = dns1;
this->last_dns2_ = dns2;
// IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot
char buf[network::IP_ADDRESS_BUFFER_SIZE * 2];
dns1.str_to(buf);
size_t len1 = strlen(buf);
buf[len1] = ' ';
dns2.str_to(buf + len1 + 1);
this->publish_state(buf);
}
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
void dump_config() override; void dump_config() override;
protected: // EthernetIPStateListener interface
network::IPAddress last_dns1_; void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
network::IPAddress last_dns2_; const network::IPAddress &dns2) override;
}; };
#endif // USE_ETHERNET_IP_STATE_LISTENERS
class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor { class MACAddressEthernetInfo final : public Component, public text_sensor::TextSensor {
public: public:
void setup() override { void setup() override {
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];

View File

@@ -1,5 +1,5 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import text_sensor from esphome.components import ethernet, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DNS_ADDRESS, CONF_DNS_ADDRESS,
@@ -13,24 +13,22 @@ DEPENDENCIES = ["ethernet"]
ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info") ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info")
IPAddressEthernetInfo = ethernet_info_ns.class_( IPAddressEthernetInfo = ethernet_info_ns.class_(
"IPAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent "IPAddressEthernetInfo", text_sensor.TextSensor, cg.Component
) )
DNSAddressEthernetInfo = ethernet_info_ns.class_( DNSAddressEthernetInfo = ethernet_info_ns.class_(
"DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent "DNSAddressEthernetInfo", text_sensor.TextSensor, cg.Component
) )
MACAddressEthernetInfo = ethernet_info_ns.class_( MACAddressEthernetInfo = ethernet_info_ns.class_(
"MACAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent "MACAddressEthernetInfo", text_sensor.TextSensor, cg.Component
) )
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
IPAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC IPAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
) ).extend(
.extend(cv.polling_component_schema("1s"))
.extend(
{ {
cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@@ -40,7 +38,7 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema(
DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")), ),
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
), ),
@@ -49,6 +47,12 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config): async def to_code(config):
# Request Ethernet IP state listener slots - one per sensor type
if CONF_IP_ADDRESS in config:
ethernet.request_ethernet_ip_state_listener()
if CONF_DNS_ADDRESS in config:
ethernet.request_ethernet_ip_state_listener()
if conf := config.get(CONF_IP_ADDRESS): if conf := config.get(CONF_IP_ADDRESS):
ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS])
await cg.register_component(ip_info, config[CONF_IP_ADDRESS]) await cg.register_component(ip_info, config[CONF_IP_ADDRESS])
@@ -57,8 +61,8 @@ async def to_code(config):
sens = await text_sensor.new_text_sensor(sensor_conf) sens = await text_sensor.new_text_sensor(sensor_conf)
cg.add(ip_info.add_ip_sensors(x, sens)) cg.add(ip_info.add_ip_sensors(x, sens))
if conf := config.get(CONF_DNS_ADDRESS): if conf := config.get(CONF_DNS_ADDRESS):
dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS]) dns_info = await text_sensor.new_text_sensor(conf)
await cg.register_component(dns_info, config[CONF_DNS_ADDRESS]) await cg.register_component(dns_info, conf)
if conf := config.get(CONF_MAC_ADDRESS): if conf := config.get(CONF_MAC_ADDRESS):
mac_info = await text_sensor.new_text_sensor(config[CONF_MAC_ADDRESS]) mac_info = await text_sensor.new_text_sensor(conf)
await cg.register_component(mac_info, config[CONF_MAC_ADDRESS]) await cg.register_component(mac_info, conf)

View File

@@ -90,9 +90,7 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
for conf in config.get(CONF_ON_EVENT, []): for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation( await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
trigger, [(cg.std_string, "event_type")], conf
)
cg.add(var.set_event_types(event_types)) cg.add(var.set_event_types(event_types))

View File

@@ -14,10 +14,10 @@ template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); } void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
}; };
class EventTrigger : public Trigger<std::string> { class EventTrigger : public Trigger<StringRef> {
public: public:
EventTrigger(Event *event) { EventTrigger(Event *event) {
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); }); event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
} }
}; };

View File

@@ -22,8 +22,8 @@ void Event::trigger(const std::string &event_type) {
return; return;
} }
this->last_event_type_ = found; this->last_event_type_ = found;
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_); ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
this->event_callback_.call(event_type); this->event_callback_.call(StringRef(found));
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY) #if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_event(this); ControllerRegistry::notify_event(this);
#endif #endif
@@ -45,7 +45,7 @@ void Event::set_event_types(const std::vector<const char *> &event_types) {
this->last_event_type_ = nullptr; // Reset when types change this->last_event_type_ = nullptr; // Reset when types change
} }
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) { void Event::add_on_event_callback(std::function<void(StringRef event_type)> &&callback) {
this->event_callback_.add(std::move(callback)); this->event_callback_.add(std::move(callback));
} }

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstring> #include <cstring>
#include <limits>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -48,13 +49,31 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
/// Return the last triggered event type, or empty StringRef if no event triggered yet. /// Return the last triggered event type, or empty StringRef if no event triggered yet.
StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); } StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); }
/// Return event type by index, or nullptr if index is out of bounds.
const char *get_event_type(uint8_t index) const {
return index < this->types_.size() ? this->types_[index] : nullptr;
}
/// Return index of last triggered event type, or max uint8_t if no event triggered yet.
uint8_t get_last_event_type_index() const {
if (this->last_event_type_ == nullptr)
return std::numeric_limits<uint8_t>::max();
// Most events have <3 types, uint8_t is sufficient for all reasonable scenarios
const uint8_t size = static_cast<uint8_t>(this->types_.size());
for (uint8_t i = 0; i < size; i++) {
if (this->types_[i] == this->last_event_type_)
return i;
}
return std::numeric_limits<uint8_t>::max();
}
/// Check if an event has been triggered. /// Check if an event has been triggered.
bool has_event() const { return this->last_event_type_ != nullptr; } bool has_event() const { return this->last_event_type_ != nullptr; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback); void add_on_event_callback(std::function<void(StringRef event_type)> &&callback);
protected: protected:
LazyCallbackManager<void(const std::string &event_type)> event_callback_; LazyCallbackManager<void(StringRef event_type)> event_callback_;
FixedVector<const char *> types_; FixedVector<const char *> types_;
private: private:

View File

@@ -160,7 +160,7 @@ void EZOSensor::loop() {
this->commands_.pop_front(); this->commands_.pop_front();
} }
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) { void EZOSensor::add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms) {
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand); std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
ezo_command->command = command; ezo_command->command = command;
ezo_command->command_type = command_type; ezo_command->command_type = command_type;
@@ -169,13 +169,17 @@ void EZOSensor::add_command_(const std::string &command, EzoCommandType command_
} }
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) { void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value); // max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
char payload[24];
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900); this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
} }
void EZOSensor::set_address(uint8_t address) { void EZOSensor::set_address(uint8_t address) {
if (address > 0 && address < 128) { if (address > 0 && address < 128) {
std::string payload = str_sprintf("I2C,%u", address); // max 8: "I2C,"(4) + uint8(3) + null
char payload[8];
snprintf(payload, sizeof(payload), "I2C,%u", address);
this->new_address_ = address; this->new_address_ = address;
this->add_command_(payload, EzoCommandType::EZO_I2C); this->add_command_(payload, EzoCommandType::EZO_I2C);
} else { } else {
@@ -194,7 +198,9 @@ void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); } void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
void EZOSensor::set_t(float value) { void EZOSensor::set_t(float value) {
std::string payload = str_sprintf("T,%0.2f", value); // max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
char payload[16];
snprintf(payload, sizeof(payload), "T,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_T); this->add_command_(payload, EzoCommandType::EZO_T);
} }
@@ -215,7 +221,9 @@ void EZOSensor::set_calibration_point_high(float value) {
} }
void EZOSensor::set_calibration_generic(float value) { void EZOSensor::set_calibration_generic(float value) {
std::string payload = str_sprintf("Cal,%0.2f", value); // exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
char payload[16];
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900); this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
} }
@@ -223,13 +231,11 @@ void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommand
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); } void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
void EZOSensor::set_led_state(bool on) { void EZOSensor::set_led_state(bool on) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
std::string to_send = "L,";
to_send += on ? "1" : "0";
this->add_command_(to_send, EzoCommandType::EZO_LED);
}
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); } void EZOSensor::send_custom(const std::string &to_send) {
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
}
} // namespace ezo } // namespace ezo
} // namespace esphome } // namespace esphome

View File

@@ -92,7 +92,7 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
std::deque<std::unique_ptr<EzoCommand>> commands_; std::deque<std::unique_ptr<EzoCommand>> commands_;
int new_address_; int new_address_;
void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300); void add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms = 300);
void set_calibration_point_(EzoCalibrationType type, float value); void set_calibration_point_(EzoCalibrationType type, float value);

View File

@@ -318,90 +318,93 @@ void EzoPMP::send_next_command_() {
switch (this->next_command_) { switch (this->next_command_) {
// Read Commands // Read Commands
case EZO_PMP_COMMAND_READ_DOSING: // Page 54 case EZO_PMP_COMMAND_READ_DOSING: // Page 54
command_buffer_length = sprintf((char *) command_buffer, "D,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,?");
break; break;
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53) case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
command_buffer_length = sprintf((char *) command_buffer, "R"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "R");
break; break;
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
command_buffer_length = sprintf((char *) command_buffer, "DC,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,?");
break; break;
case EZO_PMP_COMMAND_READ_PAUSE_STATUS: case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
command_buffer_length = sprintf((char *) command_buffer, "P,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P,?");
break; break;
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
command_buffer_length = sprintf((char *) command_buffer, "TV,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "TV,?");
break; break;
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
command_buffer_length = sprintf((char *) command_buffer, "ATV,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "ATV,?");
break; break;
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
command_buffer_length = sprintf((char *) command_buffer, "Cal,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,?");
break; break;
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
command_buffer_length = sprintf((char *) command_buffer, "PV,?"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "PV,?");
break; break;
// Non-Read Commands // Non-Read Commands
case EZO_PMP_COMMAND_FIND: // Find (page 52) case EZO_PMP_COMMAND_FIND: // Find (page 52)
command_buffer_length = sprintf((char *) command_buffer, "Find"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Find");
wait_time_for_command = 60000; // This command will block all updates for a minute wait_time_for_command = 60000; // This command will block all updates for a minute
break; break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54) case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
command_buffer_length = sprintf((char *) command_buffer, "D,*"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,*");
break; break;
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64) case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
command_buffer_length = sprintf((char *) command_buffer, "Clear"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Clear");
break; break;
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65) case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
command_buffer_length = sprintf((char *) command_buffer, "Cal,clear"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,clear");
break; break;
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61) case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
command_buffer_length = sprintf((char *) command_buffer, "P"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "P");
break; break;
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62) case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
command_buffer_length = sprintf((char *) command_buffer, "X"); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "X");
break; break;
// Non-Read commands with parameters // Non-Read commands with parameters
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55) case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_); command_buffer_length =
snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f", this->next_command_volume_);
break; break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56) case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
command_buffer_length = command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "D,%0.1f,%i",
sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_); this->next_command_volume_, this->next_command_duration_);
break; break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57) case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
command_buffer_length = command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "DC,%0.1f,%i",
sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_); this->next_command_volume_, this->next_command_duration_);
break; break;
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65) case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_); command_buffer_length =
snprintf((char *) command_buffer, sizeof(command_buffer), "Cal,%0.2f", this->next_command_volume_);
break; break;
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73) case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_); command_buffer_length =
snprintf((char *) command_buffer, sizeof(command_buffer), "I2C,%i", this->next_command_duration_);
break; break;
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_); command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_);
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer); ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
break; break;

View File

@@ -77,7 +77,7 @@ FanSpeedSetTrigger = fan_ns.class_(
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_) "FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
) )
FanPresetSetTrigger = fan_ns.class_( FanPresetSetTrigger = fan_ns.class_(
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string) "FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
) )
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
@@ -287,7 +287,7 @@ async def setup_fan_core_(var, config):
await automation.build_automation(trigger, [(cg.int_, "x")], conf) await automation.build_automation(trigger, [(cg.int_, "x")], conf)
for conf in config.get(CONF_ON_PRESET_SET, []): for conf in config.get(CONF_ON_PRESET_SET, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf) await automation.build_automation(trigger, [(cg.StringRef, "x")], conf)
async def register_fan(var, config): async def register_fan(var, config):

View File

@@ -208,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<int> {
int last_speed_; int last_speed_;
}; };
class FanPresetSetTrigger : public Trigger<std::string> { class FanPresetSetTrigger : public Trigger<StringRef> {
public: public:
FanPresetSetTrigger(Fan *state) { FanPresetSetTrigger(Fan *state) {
state->add_on_state_callback([this, state]() { state->add_on_state_callback([this, state]() {
@@ -216,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<std::string> {
auto should_trigger = preset_mode != this->last_preset_mode_; auto should_trigger = preset_mode != this->last_preset_mode_;
this->last_preset_mode_ = preset_mode; this->last_preset_mode_ = preset_mode;
if (should_trigger) { if (should_trigger) {
this->trigger(std::string(preset_mode)); this->trigger(preset_mode);
} }
}); });
this->last_preset_mode_ = state->get_preset_mode(); this->last_preset_mode_ = state->get_preset_mode();

View File

@@ -201,7 +201,7 @@ void Fan::publish_state() {
auto traits = this->get_traits(); auto traits = this->get_traits();
ESP_LOGD(TAG, ESP_LOGD(TAG,
"'%s' - Sending state:\n" "'%s' >>\n"
" State: %s", " State: %s",
this->name_.c_str(), ONOFF(this->state)); this->name_.c_str(), ONOFF(this->state));
if (traits.supports_speed()) { if (traits.supports_speed()) {

View File

@@ -163,9 +163,10 @@ bool GDK101Component::read_fw_version_(uint8_t *data) {
return false; return false;
} }
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]); // max 8: "255.255" (7 chars) + null
char buf[8];
this->fw_version_text_sensor_->publish_state(fw_version_str); snprintf(buf, sizeof(buf), "%d.%d", data[0], data[1]);
this->fw_version_text_sensor_->publish_state(buf);
} }
#endif // USE_TEXT_SENSOR #endif // USE_TEXT_SENSOR
return true; return true;

View File

@@ -1,4 +1,3 @@
#include <cstdio>
#include <cstring> #include <cstring>
#include "hmac_sha256.h" #include "hmac_sha256.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
@@ -26,9 +25,7 @@ void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_
void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); } void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); }
void HmacSHA256::get_hex(char *output) { void HmacSHA256::get_hex(char *output) {
for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) { format_hex_to(output, SHA256_DIGEST_SIZE * 2 + 1, this->digest_, SHA256_DIGEST_SIZE);
sprintf(output + (i * 2), "%02x", this->digest_[i]);
}
} }
bool HmacSHA256::equals_bytes(const uint8_t *expected) { bool HmacSHA256::equals_bytes(const uint8_t *expected) {

View File

@@ -97,7 +97,7 @@ void HomeassistantNumber::control(float value) {
entity_value.key = VALUE_KEY; entity_value.key = VALUE_KEY;
// Stack buffer - no heap allocation; %g produces shortest representation // Stack buffer - no heap allocation; %g produces shortest representation
char value_buf[16]; char value_buf[16];
snprintf(value_buf, sizeof(value_buf), "%g", value); buf_append_printf(value_buf, sizeof(value_buf), 0, "%g", value);
entity_value.value = StringRef(value_buf); entity_value.value = StringRef(value_buf);
api::global_api_server->send_homeassistant_action(resp); api::global_api_server->send_homeassistant_action(resp);

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