Compare commits

...

512 Commits

Author SHA1 Message Date
J. Nick Koston
0d56526918 Merge branch 'dev' into bh1750_loop 2026-01-07 08:35:05 -10:00
J. Nick Koston
050e9b0d4a [wifi] Add basic post-connect roaming support for stationary devices (#12809) 2026-01-07 08:30:23 -10:00
J. Nick Koston
25ac89e9b5 [logger] Add thread-safe logging for host platform (#13010) 2026-01-07 08:29:50 -10:00
J. Nick Koston
d86d1f9f52 [modbus_controller] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12781) 2026-01-07 08:29:28 -10:00
J. Nick Koston
fd19280df9 [es8388] Use index-based select publish_state to avoid heap allocations (#13053) 2026-01-07 08:29:00 -10:00
J. Nick Koston
b7dbda497a [core] Improve log timestamp accuracy by batching serial reads (#12750) 2026-01-07 08:28:31 -10:00
J. Nick Koston
815543b77e [tuya] Avoid heap allocation in text sensor enum publish (#13056) 2026-01-07 08:28:14 -10:00
J. Nick Koston
0948e0359f [core] Add integer overload for fnv1a_hash_extend (#13054) 2026-01-07 08:27:58 -10:00
J. Nick Koston
2830c7dab8 [ld2410/ld2412/ld2450] Use index-based select publish_state to avoid heap allocations (#13051) 2026-01-07 08:27:39 -10:00
J. Nick Koston
a03c13f304 [esp32_hosted] Add SHA256 alignment for hardware DMA compatibility (#13050) 2026-01-07 08:26:49 -10:00
J. Nick Koston
ef64226ed0 [mqtt] Use ESPHOME_F() for JSON strings to reduce ESP8266 RAM usage (#13049) 2026-01-07 08:26:21 -10:00
J. Nick Koston
ed39a130a8 [http_request] Store JSON keys in flash for ESP8266 (#13048) 2026-01-07 08:26:04 -10:00
J. Nick Koston
21687a1f58 [sun_gtil2] Eliminate heap allocations in text sensor publishing (#13047)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-07 08:25:33 -10:00
J. Nick Koston
bf75f77eee [preferences] Fix preferences not syncing in safe mode due to component registration order (#13041) 2026-01-07 08:25:08 -10:00
J. Nick Koston
39526e5360 [analyze-memory] Add RAM symbol analysis by component (#13040) 2026-01-07 08:24:44 -10:00
J. Nick Koston
8e40a55d5d [ble_client] Eliminate heap allocations in text sensor (#13038) 2026-01-07 08:24:22 -10:00
J. Nick Koston
20927674da [sun] Eliminate heap allocation in text sensor (#13037) 2026-01-07 08:24:09 -10:00
J. Nick Koston
8464307a43 [api] Coalesce log packets to reduce buffer pressure and prevent dropped state updates (#13026) 2026-01-07 08:23:50 -10:00
J. Nick Koston
546cdbde0d [api] Simplify string handling by removing bifurcated client/server storage (#12822) 2026-01-07 08:23:28 -10:00
tomaszduda23
ada4e6d5e9 [nrf52, zigbee] Add sensor (#12187)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-07 08:20:01 -10:00
Keith Burzinski
d6554702d8 [zwave_proxy] Make send_frame safer, make set_home_id protected (#13055) 2026-01-07 08:54:08 +00:00
Samuel Sieb
b083c33857 [espnow] fix channel validation (#13057) 2026-01-07 00:41:24 -08:00
Keith Burzinski
f8309b007c [zwave_proxy] Add logging if client sends zero-length message (#13052) 2026-01-07 01:41:33 -06:00
Clyde Stubbs
ac672e4b8f [esp32] Don't warn about no ota rollback if no ota at all (#13045) 2026-01-07 18:19:46 +11:00
J. Nick Koston
c387c03944 [text_sensor][text] Avoid heap allocation when state unchanged (#13044) 2026-01-06 19:22:04 -10:00
J. Nick Koston
fb47bfe92a [dsmr] Eliminate heap allocation when publishing telegram (#13032) 2026-01-06 17:54:20 -10:00
J. Nick Koston
5b9be7c169 [ci] Add lint check to prevent usage of deprecated CORE.using_esp_idf (#13029) 2026-01-06 17:54:04 -10:00
J. Nick Koston
6d1f6a1084 [wifi_info] Eliminate heap churn in text sensors (#13031) 2026-01-06 17:53:54 -10:00
J. Nick Koston
f9ed2aa17f [pylontech] Eliminate heap allocations in text sensors (#13033) 2026-01-06 17:53:42 -10:00
J. Nick Koston
35118da606 [ethernet_info] Eliminate heap allocations in text sensors (#13034) 2026-01-06 17:53:29 -10:00
J. Nick Koston
498477c5a2 [homeassistant] Eliminate heap allocation in text sensor state updates (#13035) 2026-01-06 17:53:14 -10:00
J. Nick Koston
3a84e4a0b4 [openthread_info] Eliminate heap allocations in text sensors (#13036) 2026-01-06 17:53:00 -10:00
J. Nick Koston
4391457a96 [sml] Eliminate heap allocations in text sensor (#13039) 2026-01-06 17:51:26 -10:00
Kyrill
68b4bc9d9e Map HEAT_COOL to MODE_AUTO in HeatpumpIR component (#12058)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-06 21:28:41 -06:00
J. Nick Koston
b052c9f562 [esp32_camera][uart] Add missing wake_loop_threadsafe() preprocessor guards (#13043) 2026-01-07 03:22:10 +00:00
J. Nick Koston
a19597626b [text_sensor][text] Add const char* overloads to publish_state to eliminate heap churn (#13030) 2026-01-06 16:16:37 -10:00
J. Nick Koston
2147ddf8c7 [api] Eliminate std::string from ClientInfo struct (#12566)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2026-01-06 21:32:23 +00:00
Jas Strong
412ab5dbbf [aqi] Implement a sensor that computes AQI (#12958)
Co-authored-by: jas <jas@asspa.in>
2026-01-06 16:31:50 -05:00
David Woodhouse
4419bf02b1 [async_tcp] Fix build conflicts and use IDF component for ESP32 (#13025)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-06 20:26:27 +00:00
J. Nick Koston
8eb28a7724 [neopixelbus] Fix ESP8266 compilation by enabling Serial/Serial1 for NeoPixelBus library (#13027) 2026-01-06 18:38:39 +00:00
J. Nick Koston
d6c2dd3c26 [wifi] Eliminate heap allocations in IP address logging (#13017) 2026-01-06 08:21:16 -10:00
J. Nick Koston
ac42102320 [core] Auto-replace / in entity names with Unicode fraction slash during deprecation period (#13016) 2026-01-06 07:36:01 -10:00
J. Nick Koston
2c6584baf5 [xiaomi_ble] Simplify set_bindkey using parse_hex and const char* (#13014) 2026-01-06 07:35:40 -10:00
J. Nick Koston
c1ad39a072 [wifi] Clean up duplicate and empty logging output (#13018) 2026-01-06 07:35:16 -10:00
J. Nick Koston
d3e193cd71 [ota] Fix ESP32-S3 OTA crash with hardware SHA acceleration on IDF 5.5.x (#13021) 2026-01-06 07:34:58 -10:00
J. Nick Koston
11aed601b8 [ble_scanner] Use stack-based string formatting to reduce heap allocations (#13013) 2026-01-06 07:34:38 -10:00
J. Nick Koston
e0981323bd [mqtt] Move Home Assistant discovery keys to PROGMEM on ESP8266 (#13011) 2026-01-06 07:33:56 -10:00
J. Nick Koston
1e56325b33 [improv_base] Optimize next_url to avoid STL string operations (#13015) 2026-01-06 07:33:32 -10:00
J. Nick Koston
a8a26f4ea8 [opentherm][nau7802] Use direct format specifiers instead of to_string().c_str() (#13019) 2026-01-06 07:32:43 -10:00
Jonathan Swoboda
a94eef3a60 Merge branch 'release' into dev 2026-01-06 10:23:02 -05:00
Jonathan Swoboda
b6f3a5d8b7 Merge pull request #13024 from esphome/bump-2025.12.5
2025.12.5
2026-01-06 10:22:48 -05:00
Jonathan Swoboda
3322b04e00 Bump version to 2025.12.5 2026-01-06 09:35:38 -05:00
Jonathan Swoboda
47d0d3cfeb [cc1101] Add PLL lock verification and retry support (#13006) 2026-01-06 09:35:37 -05:00
Clyde Stubbs
8255c02d5d [esp32_ble] Remove requirement for configured network (#12891) 2026-01-06 09:35:37 -05:00
Conrad Juhl Andersen
8b4ba8dfe6 [wts01] Fix negative values for WTS01 sensor (#12835) 2026-01-06 09:35:37 -05:00
Artur
178a61b6fd [sn74hc595]: fix 'Attempted read from write-only channel' when using esp-idf framework (#12801) 2026-01-06 09:35:37 -05:00
Clyde Stubbs
b5df4cdf1d [lvgl] Fix arc background angles (#12773) 2026-01-06 09:35:37 -05:00
Jonathan Swoboda
484f4b3aad [cc1101] Add PLL lock verification and retry support (#13006) 2026-01-06 09:34:28 -05:00
J. Nick Koston
22cb0da903 [radon_eye_rd200, radon_eye_ble] Use stack-based string formatting in logging (#12991) 2026-01-05 17:45:51 -10:00
Jas Strong
28cf3b7a9b [rd03d] Add Ai-Thinker RD-03D mmWave radar component (#12764)
Co-authored-by: jas <jas@asspa.in>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-05 22:35:32 -05:00
J. Nick Koston
84dd17187d [pvvx_mithermometer] Reduce heap allocations with stack-based string formatting (#12994) 2026-01-05 17:35:22 -10:00
J. Nick Koston
110c892c3c [esp8266] Avoid heap allocation in preferences save/load (#12465)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-05 17:35:04 -10:00
J. Nick Koston
8518424a88 [esp8266] Add enable_serial/enable_serial1 helpers to exclude unused Serial objects (#12736) 2026-01-05 17:26:49 -10:00
J. Nick Koston
7ba4dc0f1a [airthings_wave_base, airthings_ble] Use stack-based string formatting in logging (#12989) 2026-01-05 17:22:27 -10:00
J. Nick Koston
95573bc106 [mopeka] Reduce heap allocations with stack-based string formatting (#12990) 2026-01-05 17:21:54 -10:00
J. Nick Koston
a6adc29b14 [xiaomi_ble] Reduce heap allocations with stack-based string formatting (#12992) 2026-01-05 17:20:51 -10:00
J. Nick Koston
8251513556 [bedjet] Use stack-based UUID formatting in logging (#12993) 2026-01-05 17:19:34 -10:00
J. Nick Koston
e6e0be3345 [bthome_mithermometer] Reduce heap allocations with stack-based string formatting (#12995) 2026-01-05 17:18:58 -10:00
J. Nick Koston
64da6d46e9 [ruuvi_ble] Reduce heap allocation with stack-based string formatting (#12997) 2026-01-05 17:18:06 -10:00
J. Nick Koston
9b9a341db0 [b_parasite] Reduce heap allocation with stack-based string formatting (#12998) 2026-01-05 17:17:37 -10:00
J. Nick Koston
18217fbe10 [atc_mithermometer] Reduce heap allocations with stack-based string formatting (#12996) 2026-01-05 17:16:47 -10:00
J. Nick Koston
c3e6a4178c [thermopro_ble] Reduce heap allocation with stack-based string formatting (#12999) 2026-01-05 17:16:14 -10:00
J. Nick Koston
2d4cd4ce7e [midea] Reduce heap allocations with stack-based string formatting (#13000) 2026-01-05 17:15:50 -10:00
J. Nick Koston
0290ed5d23 [voice_assistant] Reduce heap allocation with stack-based timer formatting (#13001) 2026-01-05 17:14:33 -10:00
Evaldas Auryla
b402e403a0 [radon_eye_rd200] update Radon Eye RD200 with v2/v3 support (#7962)
Co-authored-by: Artem Butusov <art.sormy@gmail.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-05 16:34:23 -10:00
Jonathan Swoboda
b2c22a02b1 [cc1101] Add freq_offset to on_packet trigger (#13008) 2026-01-05 20:08:07 -05:00
PolarGoose
7ed4922d28 [dsmr] Remove dependency on Arduino framework. Various bug fixes. Add missing sensors. (#11036)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-06 00:18:54 +00:00
Clyde Stubbs
21aa245cff [image] Replace use of cairosvg with resvg-py (#12863)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-06 10:56:59 +11:00
David Woodhouse
94bedd83be async_tcp: Add AsyncClient for ESP-IDF and host (#12337)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-05 23:37:38 +00:00
guillempages
c8f5a97cef [esphome OTA] Allow compilation on host platform (#11655)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-05 23:33:06 +00:00
J. Nick Koston
fc7e55bfdc [api] Avoid heap string copies in Home Assistant state subscription callbacks (#12506) 2026-01-05 07:42:18 -10:00
J. Nick Koston
6aaaae5d0e [ci] Add LibreTiny (BK72XX) to memory impact analysis (#12983)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-05 07:40:49 -10:00
J. Nick Koston
e87a3b3916 [light] Use zero-copy set_effect overload in JSON schema parsing (#12979) 2026-01-05 07:40:24 -10:00
J. Nick Koston
3fb5b28930 [captive_portal] Avoid defer overhead on ESP8266 when saving WiFi credentials (#12981) 2026-01-05 07:40:04 -10:00
J. Nick Koston
1bb4be435c [esp32_ble_tracker, ble_client] Reduce heap allocations with stack-based string formatting (#12982) 2026-01-05 07:39:37 -10:00
J. Nick Koston
0990a9c2b0 [esp32_ble] Avoid heap allocation in ESPBTUUID::from_raw for string literals (#12980) 2026-01-05 07:39:24 -10:00
Samuel Schultze
086eb4b930 [whirlpool] support for 14 byte whirlpool IR receiver messages (#12774)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-05 11:45:32 -05:00
J. Nick Koston
d107b37d3b [st7735] Combine log statements to reduce loop blocking (#12977) 2026-01-04 17:51:02 -10:00
J. Nick Koston
80ab9485e0 [spi_led_strip] Combine log statements to reduce loop blocking (#12968) 2026-01-04 17:00:59 -10:00
J. Nick Koston
28d30fdddb [ssd1306_i2c] Combine log statements to reduce loop blocking (#12969) 2026-01-04 17:00:40 -10:00
J. Nick Koston
0bd8a7e1a0 [ssd1306_spi] Combine log statements to reduce loop blocking (#12970) 2026-01-04 17:00:21 -10:00
J. Nick Koston
2381ea7ff5 [ssd1322_spi] Combine log statements to reduce loop blocking (#12971) 2026-01-04 17:00:09 -10:00
J. Nick Koston
06101c54a5 [ssd1327_spi] Combine log statements to reduce loop blocking (#12973) 2026-01-04 16:59:52 -10:00
J. Nick Koston
ed332a034b [ssd1351_spi] Combine log statements to reduce loop blocking (#12974) 2026-01-04 16:59:36 -10:00
J. Nick Koston
a2bb9468ff [sm2235] Combine log statements to reduce loop blocking (#12964)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-05 02:57:43 +00:00
J. Nick Koston
d8387799d9 [sm2335] Combine log statements to reduce loop blocking (#12965) 2026-01-04 16:56:30 -10:00
J. Nick Koston
ae3cdeda99 [ssd1325_spi] Combine log statements to reduce loop blocking (#12972) 2026-01-04 16:55:55 -10:00
J. Nick Koston
9cd003034c [spi_device] Combine log statements to reduce loop blocking (#12967) 2026-01-04 16:55:31 -10:00
J. Nick Koston
f67a8d0d1f [sonoff_d1] Combine log statements to reduce loop blocking (#12966) 2026-01-04 16:55:11 -10:00
J. Nick Koston
47223965b6 [sm2135] Combine log statements to reduce loop blocking (#12963) 2026-01-04 16:54:17 -10:00
J. Nick Koston
9128fc3120 [sm16716] Combine log statements to reduce loop blocking (#12962) 2026-01-04 16:54:03 -10:00
J. Nick Koston
c742db48b8 [sim800l] Combine log statements to reduce loop blocking (#12961) 2026-01-04 16:52:57 -10:00
J. Nick Koston
4bc1a02fc2 [shtcx] Combine log statements to reduce loop blocking (#12960) 2026-01-04 16:52:03 -10:00
J. Nick Koston
1d0f36ba35 [st7789v] Combine log statements to reduce loop blocking (#12978) 2026-01-04 16:51:37 -10:00
J. Nick Koston
405b26426c [st7567_spi] Combine log statements to reduce loop blocking (#12976) 2026-01-04 16:51:24 -10:00
J. Nick Koston
2295f57dec [st7567_i2c] Combine log statements to reduce loop blocking (#12975) 2026-01-04 16:51:11 -10:00
J. Nick Koston
a011d5ea96 [sht3xd] Combine log statements to reduce loop blocking (#12957) 2026-01-05 02:14:57 +00:00
tomaszduda23
12027569d3 [nrf52,zigbee] add support for binary_input (#11535)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-04 21:11:14 -05:00
J. Nick Koston
ab0e15e4bb [runtime_stats] Combine log statements to reduce loop blocking (#12954) 2026-01-04 16:10:51 -10:00
J. Nick Koston
9f7925c1d5 [safe_mode] Combine log statements to reduce loop blocking (#12955) 2026-01-04 16:10:19 -10:00
J. Nick Koston
7449421cea [shelly_dimmer] Combine log statements to reduce loop blocking (#12956) 2026-01-04 16:10:06 -10:00
J. Nick Koston
4f20c1ceb1 [rp2040_pwm] Combine log statements to reduce loop blocking (#12952) 2026-01-04 16:09:51 -10:00
J. Nick Koston
452fcd56dd [remote_receiver] Combine log statements to reduce loop blocking (#12951) 2026-01-04 16:08:45 -10:00
J. Nick Koston
44fc156ef6 [remote_base] Combine log statements to reduce loop blocking (#12950) 2026-01-04 16:07:50 -10:00
J. Nick Koston
3ec05a5a13 [radon_eye_rd200] Combine log statements to reduce loop blocking (#12949) 2026-01-04 16:06:55 -10:00
J. Nick Koston
e6a630ae64 [qmp6988] Combine log statements to reduce loop blocking (#12947) 2026-01-04 16:06:34 -10:00
J. Nick Koston
3c8fd5c5c0 [pulse_counter] Combine log statements to reduce loop blocking (#12946) 2026-01-04 16:05:05 -10:00
J. Nick Koston
a635c82830 [pid] Combine log statements to reduce loop blocking (#12942) 2026-01-04 16:04:50 -10:00
J. Nick Koston
0b9fcf9ed3 [pn532] Combine log statements to reduce loop blocking (#12943) 2026-01-04 14:46:21 -10:00
J. Nick Koston
2d8abbb2ac [pn7150] Combine log statements to reduce loop blocking (#12944) 2026-01-04 14:46:09 -10:00
J. Nick Koston
6d8142c539 [rpi_dpi_rgb] Combine log statements to reduce loop blocking (#12953) 2026-01-04 14:45:52 -10:00
J. Nick Koston
50f27cdd77 [pn7160] Combine log statements to reduce loop blocking (#12945) 2026-01-04 14:45:38 -10:00
J. Nick Koston
6c809583d3 [qspi_dbi] Combine log statements to reduce loop blocking (#12948)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-04 14:45:22 -10:00
J. Nick Koston
f41f0506c1 [pcf8574] Combine log statements to reduce loop blocking (#12941) 2026-01-04 14:05:17 -10:00
J. Nick Koston
850f189225 [api] Fix message batch size mismatch and improve naming consistency (#12940) 2026-01-04 23:44:49 +00:00
J. Nick Koston
a37d4b17eb [wifi] Combine log statements to reduce loop blocking (#12939) 2026-01-04 23:11:48 +00:00
J. Nick Koston
7309a65167 [tlc5971] Combine log statements to reduce loop blocking (#12922) 2026-01-04 13:11:08 -10:00
J. Nick Koston
7fde110ac5 [voice_assistant] Combine log statements to reduce loop blocking (#12930) 2026-01-04 13:04:22 -10:00
J. Nick Koston
9ed107bc33 [xgzp68xx] Combine log statements to reduce loop blocking (#12935) 2026-01-04 13:04:08 -10:00
J. Nick Koston
b291f359ae [x9c] Combine log statements to reduce loop blocking (#12934) 2026-01-04 13:03:56 -10:00
J. Nick Koston
161545584d [wl_134] Combine log statements to reduce loop blocking (#12933) 2026-01-04 13:03:42 -10:00
J. Nick Koston
3ea11d4e59 [xpt2046] Combine log statements to reduce loop blocking (#12937) 2026-01-04 13:03:22 -10:00
J. Nick Koston
5713d69efe [ufire_ec] Combine log statements to reduce loop blocking (#12925) 2026-01-04 13:03:06 -10:00
J. Nick Koston
9d9f9c3c84 [xiaomi_xmwsdj04mmc] Combine log statements to reduce loop blocking (#12936) 2026-01-04 13:02:52 -10:00
J. Nick Koston
29d332af92 [wireguard] Combine log statements to reduce loop blocking (#12932) 2026-01-04 13:02:02 -10:00
J. Nick Koston
c44d095f8a [usb_host] Combine log statements to reduce loop blocking (#12927) 2026-01-04 13:01:49 -10:00
J. Nick Koston
0b996616b8 [waveshare_epaper] Combine log statements to reduce loop blocking (#12931) 2026-01-04 13:01:33 -10:00
J. Nick Koston
557b6a9ef0 [sun] Combine log statements to reduce loop blocking (#12919) 2026-01-04 13:00:59 -10:00
J. Nick Koston
6e633f7f3b [usb_uart] Combine log statements to reduce loop blocking (#12928) 2026-01-04 13:00:03 -10:00
J. Nick Koston
c59455e445 [mqtt] Combine log statements to reduce loop blocking (#12938) 2026-01-04 12:59:47 -10:00
J. Nick Koston
32b3d27c7c [uln2003] Combine log statements to reduce loop blocking (#12926) 2026-01-04 12:58:42 -10:00
J. Nick Koston
88cb5d9671 [tmp1075] Combine log statements to reduce loop blocking (#12923) 2026-01-04 12:58:20 -10:00
J. Nick Koston
56d1d928f9 [tlc5947] Combine log statements to reduce loop blocking (#12921) 2026-01-04 12:57:10 -10:00
J. Nick Koston
e9cab96cb7 [sx1509] Combine log statements to reduce loop blocking (#12920) 2026-01-04 12:56:50 -10:00
J. Nick Koston
022c42f9ca [tuya] Combine log statements to reduce loop blocking (#12924) 2026-01-04 12:53:58 -10:00
J. Nick Koston
25ef9aff04 [vl53l0x] Combine log statements to reduce loop blocking (#12929) 2026-01-04 12:53:46 -10:00
Clyde Stubbs
71940acc49 [esp32_ble] Remove requirement for configured network (#12891) 2026-01-04 22:37:44 +00:00
J. Nick Koston
05695affff [m5stack_8angle] Combine log statements to reduce loop blocking (#12908) 2026-01-04 11:55:31 -10:00
J. Nick Koston
f2308c77c6 [libretiny_pwm] Combine log statements to reduce loop blocking (#12907) 2026-01-04 11:55:18 -10:00
J. Nick Koston
a5368d1d95 [modbus] Combine log statements to reduce loop blocking (#12910) 2026-01-04 11:54:47 -10:00
J. Nick Koston
b8d93f2150 [mopeka_std_check] Combine log statements to reduce loop blocking (#12911) 2026-01-04 11:54:31 -10:00
J. Nick Koston
ca574a1550 [ledc] Combine log statements to reduce loop blocking (#12906) 2026-01-04 11:54:14 -10:00
J. Nick Koston
b0855b4a0e [lc709203f] Combine log statements to reduce loop blocking (#12905) 2026-01-04 11:53:50 -10:00
J. Nick Koston
1fccddf67f [ina2xx_base] Combine log statements to reduce loop blocking (#12904) 2026-01-04 11:52:56 -10:00
J. Nick Koston
548600b47a [ina260] Combine log statements to reduce loop blocking (#12903) 2026-01-04 11:52:34 -10:00
J. Nick Koston
9bbfad4a08 [honeywellabp] Combine log statements to reduce loop blocking (#12902) 2026-01-04 11:52:08 -10:00
J. Nick Koston
8ae1f26b6a [hlw8012] Combine log statements to reduce loop blocking (#12901) 2026-01-04 11:51:45 -10:00
J. Nick Koston
9b2a36a313 [hc8] Combine log statements to reduce loop blocking (#12900) 2026-01-04 11:51:33 -10:00
J. Nick Koston
aa4b274b3c [mcp3204] Combine log statements to reduce loop blocking (#12912) 2026-01-04 11:51:18 -10:00
J. Nick Koston
d1d5c942ec [mcp9600] Combine log statements to reduce loop blocking (#12913) 2026-01-04 11:51:01 -10:00
J. Nick Koston
ccc9d95c9d [mqtt] Combine log statements to reduce loop blocking (#12914) 2026-01-04 11:28:14 -10:00
J. Nick Koston
6d9d593e12 [my9231] Combine log statements to reduce loop blocking (#12915) 2026-01-04 11:27:14 -10:00
J. Nick Koston
fc9683f024 [opentherm] Combine log statements to reduce loop blocking (#12916) 2026-01-04 11:26:13 -10:00
J. Nick Koston
61ecfb5f2b [openthread] Combine log statements to reduce loop blocking (#12917) 2026-01-04 11:25:52 -10:00
J. Nick Koston
7e75826064 [wifi] Fix LibreTiny thread safety with queue-based event handling (#12833) 2026-01-04 11:25:24 -10:00
J. Nick Koston
8287484a36 [gl_r01_i2c] Combine log statements to reduce loop blocking (#12899) 2026-01-04 11:24:51 -10:00
J. Nick Koston
dd8259b2ce [gcja5] Combine log statements to reduce loop blocking (#12898) 2026-01-04 11:24:36 -10:00
Stuart Parmenter
449e478bec [hub75] Bump esp-hub75 version to 0.2.2 (#12674) 2026-01-04 10:50:10 -10:00
Jonathan Swoboda
9ae19d53dc [ultrasonic] Fix timeout issues and deprecate timeout option (#12897)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-04 13:39:56 -05:00
J. Nick Koston
77b3ffee00 [factory_reset] Combine log statements to reduce loop blocking (#12866) 2026-01-04 08:34:16 -10:00
J. Nick Koston
dff8dc0ed1 [cc1101] Combine log statements to reduce loop blocking (#12869) 2026-01-04 08:34:07 -10:00
J. Nick Koston
5a8b0f59b8 [cd74hc4067] Combine log statements to reduce loop blocking (#12870) 2026-01-04 08:33:58 -10:00
J. Nick Koston
25a325da61 [current_based] Combine log statements to reduce loop blocking (#12873) 2026-01-04 08:33:49 -10:00
J. Nick Koston
a6db5a2ed8 [dfrobot_sen0395] Combine log statements to reduce loop blocking (#12876) 2026-01-04 08:33:38 -10:00
J. Nick Koston
9e5dbb073a [emmeti] Combine log statements to reduce loop blocking (#12878) 2026-01-04 08:31:14 -10:00
J. Nick Koston
cf513975f3 [ens160_base] Combine log statements to reduce loop blocking (#12880) 2026-01-04 08:30:45 -10:00
J. Nick Koston
1e70091a27 [esp32_hosted] Combine log statements to reduce loop blocking (#12884) 2026-01-04 08:28:17 -10:00
J. Nick Koston
766826cc9c [esp32][libretiny] Reuse preference buffer to avoid heap churn (#12890) 2026-01-04 08:28:01 -10:00
J. Nick Koston
8a4ee19c0b [es8388] Combine log statements to reduce loop blocking (#12882) 2026-01-04 08:26:19 -10:00
J. Nick Koston
b1f9c08f51 [esp32_ble_tracker] Make start_scan action idempotent (#12864) 2026-01-04 08:11:36 -10:00
J. Nick Koston
facf4777a4 [ezo_pmp] Combine log statements to reduce loop blocking (#12888) 2026-01-03 18:04:00 -10:00
J. Nick Koston
096de869b6 [esp32_ble_client] Combine log statements to reduce loop blocking (#12883) 2026-01-03 18:01:55 -10:00
J. Nick Koston
c59314ec09 [debug] Combine log statements to reduce loop blocking (#12875) 2026-01-03 18:01:28 -10:00
J. Nick Koston
e94158a12f [fan] Combine log statements to reduce loop blocking (#12889) 2026-01-03 18:00:52 -10:00
J. Nick Koston
cb598c43e8 [endstop] Combine log statements to reduce loop blocking (#12879) 2026-01-03 18:00:31 -10:00
J. Nick Koston
6e8817cbc4 [esp8266_pwm] Combine log statements to reduce loop blocking (#12885) 2026-01-03 18:00:11 -10:00
J. Nick Koston
9f06f046d6 [espnow] Combine log statements to reduce loop blocking (#12887) 2026-01-03 17:59:53 -10:00
J. Nick Koston
44fa6bae95 [dht] Combine log statements to reduce loop blocking (#12877) 2026-01-03 17:57:53 -10:00
J. Nick Koston
bc9093127e [cap1188] Combine log statements to reduce loop blocking (#12868) 2026-01-03 17:00:14 -10:00
J. Nick Koston
cf93b66306 [chsc6x] Combine log statements to reduce loop blocking (#12871) 2026-01-03 16:59:55 -10:00
J. Nick Koston
16ada4d477 [epaper_spi] Combine log statements to reduce loop blocking (#12881) 2026-01-04 02:48:39 +00:00
J. Nick Koston
c96d0015a0 [esp_ldo] Combine log statements to reduce loop blocking (#12886) 2026-01-04 02:48:04 +00:00
J. Nick Koston
12c6f5749e [cst816] Combine log statements to reduce loop blocking (#12872) 2026-01-04 02:46:29 +00:00
Douwe
5f1eacf4ec [water_heater] (4/4) Implement tests for new water_heater component (#12517)
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-03 16:43:31 -10:00
J. Nick Koston
5d384c77c5 [esp32] Move heap functions to flash, saving ~6KB (#12862) 2026-01-03 16:00:50 -10:00
dependabot[bot]
32562ca991 Bump aioesphomeapi from 43.10.0 to 43.10.1 (#12865)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-04 01:59:03 +00:00
J. Nick Koston
6b4b1272db [binary_sensor] Combine log statements to reduce loop blocking (#12849) 2026-01-04 01:56:52 +00:00
J. Nick Koston
7b74f94360 [wifi] Combine log statements to reduce loop blocking (#12856)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-04 01:54:56 +00:00
J. Nick Koston
997ab553c1 [ac_dimmer] Combine log statements to reduce loop blocking (#12840)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-04 01:36:08 +00:00
Frederic Meeuwissen
8b80fe9c6b [esp32_rmt_led_strip] Support inverted logic (#12825)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-03 20:32:27 -05:00
J. Nick Koston
ee65f2f0cd [adc] Combine log statements to reduce loop blocking (#12841) 2026-01-03 15:24:41 -10:00
J. Nick Koston
723ccd7547 [ade7880] Combine log statements to reduce loop blocking (#12842) 2026-01-03 15:05:41 -10:00
J. Nick Koston
102862e99d [ads1115] Combine log statements to reduce loop blocking (#12843) 2026-01-03 15:05:29 -10:00
J. Nick Koston
9cb265347c [ads1118] Combine log statements to reduce loop blocking (#12844) 2026-01-03 15:05:15 -10:00
J. Nick Koston
d84562f878 [anova] Combine log statements to reduce loop blocking (#12845) 2026-01-03 15:04:57 -10:00
J. Nick Koston
6bbee3cfc6 [as3935] Combine log statements to reduce loop blocking (#12846) 2026-01-03 15:04:38 -10:00
J. Nick Koston
41e7ecb29f [bedjet] Combine log statements to reduce loop blocking (#12848) 2026-01-03 15:04:21 -10:00
J. Nick Koston
0196d6ee55 [ble_nus] Combine log statements to reduce loop blocking (#12850) 2026-01-03 15:03:44 -10:00
J. Nick Koston
ea848db683 [bp1658cj] Combine log statements to reduce loop blocking (#12851) 2026-01-03 15:03:20 -10:00
J. Nick Koston
41a188ac35 [ac_dimmer] Fix ESP8266 build by requiring waveform support (#12852) 2026-01-03 15:03:01 -10:00
J. Nick Koston
8ddfeb2d38 [captive_portal] Combine log statements to reduce loop blocking (#12853) 2026-01-03 15:02:26 -10:00
J. Nick Koston
d364432e3a [uart] Combine log statements to reduce loop blocking (#12855) 2026-01-03 15:02:12 -10:00
J. Nick Koston
2a6b192af8 [ethernet] Combine log statements to reduce loop blocking (#12854) 2026-01-03 15:01:35 -10:00
J. Nick Koston
07a581e13a [update] Combine log statements to reduce loop blocking (#12857) 2026-01-03 15:01:24 -10:00
J. Nick Koston
5f5edf90e9 [water_heater] Combine log statements to reduce loop blocking (#12858) 2026-01-03 15:01:12 -10:00
J. Nick Koston
5e24469ce3 [http_request] Combine log statements to reduce loop blocking (#12859) 2026-01-03 15:01:01 -10:00
J. Nick Koston
d7a1ac83ca [esp32_ble_tracker] Combine log statements to reduce loop blocking (#12860) 2026-01-03 15:00:51 -10:00
Douwe
f11abc7dbf [water_heater] (2/4) Implement template for new water_heater component (#12516)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-03 14:45:49 -10:00
tomaszduda23
ec05692f0d [nrf52] add printk doc (#12839) 2026-01-04 00:12:31 +00:00
J. Nick Koston
2e2e54811a [absolute_humidity] Combine log statements to reduce loop blocking (#12838) 2026-01-03 17:52:23 -06:00
Clyde Stubbs
c29aa61e2a [image] Use alternative version of CairoSVG on Windows (#12811) 2026-01-04 10:08:47 +11:00
J. Nick Koston
cb3edfc654 [wifi] Use stack-based MAC formatting in ESP8266 and IDF event handlers (#12834) 2026-01-03 12:32:22 -10:00
J. Nick Koston
6685fa1da9 [core] Fix startup delay from setup timing logs when console connected (#12832) 2026-01-03 12:32:10 -10:00
J. Nick Koston
d505f0316b [wifi] Combine scan result log lines to reduce loop blocking with many matching APs (#12830) 2026-01-03 12:31:58 -10:00
J. Nick Koston
9781073f2a [espnow] Use stack-based MAC formatting and remove dead code (#12836) 2026-01-03 12:31:38 -10:00
John Hollowell
0a0501c140 Fix comment typos (#12828) 2026-01-03 17:11:48 -05:00
Jasper van der Neut - Stulen
a6e9aa7876 [mhz19] Refactor Actions to Parented (#12837) 2026-01-03 17:11:02 -05:00
Conrad Juhl Andersen
ede7391582 [wts01] Fix negative values for WTS01 sensor (#12835) 2026-01-03 17:06:33 -05:00
Jasper van der Neut - Stulen
5cfcf8d104 [mhz19] Make detection range configurable (#12677)
Co-authored-by: Fabio Pugliese Ornellas <fabio.ornellas@gmail.com>
2026-01-03 15:51:48 -05:00
J. Nick Koston
c34665f650 [api] Fix KeyError when running logs after password removal (#12831) 2026-01-03 19:13:07 +00:00
Mariusz Kryński
69867bf818 [nrf52, zephyr] move nrf52-specific code to nrf52 component (#12582)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-03 18:58:56 +00:00
J. Nick Koston
1d323c2d71 [api] Remove deprecated password authentication (#12819) 2026-01-03 07:14:48 -10:00
tomaszduda23
95a7356ea0 [uart] make sure that all variables are initialized (#12823) 2026-01-03 03:43:17 -06:00
J. Nick Koston
89b550b74a [tests] Remove reserved / character from entity names in component tests (#12820) 2026-01-03 01:00:46 -06:00
dependabot[bot]
538c6544a0 Bump ruamel-yaml from 0.18.17 to 0.19.1 (#12768)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-02 20:51:56 -10:00
dependabot[bot]
98e3695c89 Bump aioesphomeapi from 43.9.1 to 43.10.0 (#12821)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-03 06:45:17 +00:00
J. Nick Koston
00fd4f2fdd [esp8266] Exclude unused waveform code to save ~596 bytes RAM (#12690) 2026-01-02 19:51:07 -10:00
J. Nick Koston
2a5be725c8 [api] Enable zero-copy bytes SOURCE_BOTH messages (#12816) 2026-01-02 19:50:30 -10:00
Robert Klep
c4d339a4c9 [core] Add CONF_ON_START (#12439) (#12440) 2026-01-02 23:42:18 -05:00
J. Nick Koston
6409970f6e [uponor_smatrix] Use stack-based hex formatting in verbose logging (#12797)
Co-authored-by: Stefan Rado <628587+kroimon@users.noreply.github.com>
2026-01-02 16:41:02 -10:00
J. Nick Koston
bc1af007b4 [vbus] Use stack-based hex formatting in verbose logging (#12796) 2026-01-02 16:40:47 -10:00
Thomas Rupprecht
c3ffc1635d [gps] add icon for HDOP and use correct state_class for longitude and… (#12718) 2026-01-02 21:40:28 -05:00
J. Nick Koston
016eeef04a [tee501] Use stack-based hex formatting in verbose logging (#12795) 2026-01-02 16:40:06 -10:00
J. Nick Koston
ace48464a8 [addressable_light] Use StringRef to avoid allocation when saving effect name (#12759) 2026-01-02 16:39:44 -10:00
J. Nick Koston
64ba376330 [hte501] Use stack-based hex formatting in verbose logging (#12794) 2026-01-02 16:37:38 -10:00
J. Nick Koston
d946ddabfd [xiaomi_ble] Use stack-based hex formatting in verbose logging (#12793) 2026-01-02 16:37:16 -10:00
J. Nick Koston
a57011b50b [kuntze] Use stack buffer for hex formatting in verbose logging (#12775) 2026-01-02 16:36:57 -10:00
J. Nick Koston
1240e7907e [api] Use stack-based format_hex_pretty_to for packet logging macros (#12788) 2026-01-02 16:35:44 -10:00
J. Nick Koston
f0391f0213 [api] Remove object_id from API protocol - clients compute it from name #12698 (#12818) 2026-01-02 16:32:46 -10:00
J. Nick Koston
3cc6810be5 [core] Remove object_id RAM storage - no longer in hot path after #12627 (#12631) 2026-01-02 15:46:01 -10:00
J. Nick Koston
916370a943 [gpio] Avoid heap allocation in dump_summary (#12760) 2026-01-02 15:42:56 -10:00
J. Nick Koston
e2f45c590e [esp32_improv] Use stack buffer for hex formatting in verbose logging (#12737) 2026-01-02 14:28:38 -10:00
J. Nick Koston
7d21411ca4 [epaper_spi] Use stack buffer for hex formatting in command logging (#12734) 2026-01-02 14:27:00 -10:00
J. Nick Koston
56ed5af27d [nextion] Use stack buffers for hex formatting in upload logging (#12733) 2026-01-02 14:26:14 -10:00
J. Nick Koston
c8241b0122 [sonoff_d1] Use stack buffer for hex formatting in logging (#12730) 2026-01-02 14:25:02 -10:00
J. Nick Koston
30efd7fb07 [jsn_sr04t] Use stack buffer for hex formatting in error logging (#12729) 2026-01-02 14:24:47 -10:00
J. Nick Koston
1703343694 [a02yyuw] Use stack buffer for hex formatting in error logging (#12728) 2026-01-02 14:24:30 -10:00
J. Nick Koston
7fa04b6c25 [a01nyub] Use stack buffer for hex formatting in error logging (#12727) 2026-01-02 14:23:33 -10:00
J. Nick Koston
61b6476de4 [opentherm] Replace heap-allocating format calls with printf format specifiers in debug_error (#12726) 2026-01-02 14:23:18 -10:00
J. Nick Koston
b4e5e0bc9b [rc522] Use stack buffers for hex formatting in tag logging (#12725) 2026-01-02 14:22:58 -10:00
J. Nick Koston
f9b4e0e489 [remote_base] Use stack buffer for hex formatting in haier protocol logging (#12723) 2026-01-02 14:22:26 -10:00
J. Nick Koston
9ccb100cca [remote_base] Use stack buffer for hex formatting in mirage protocol logging (#12722) 2026-01-02 14:21:42 -10:00
J. Nick Koston
20b66cba23 [shelly_dimmer] Use stack buffer for hex formatting in command logging (#12721) 2026-01-02 14:21:23 -10:00
J. Nick Koston
b711172b33 [wifi] Use precision format specifier for SSID logging to avoid stack copy (#12704) 2026-01-02 14:21:09 -10:00
J. Nick Koston
0c4184b129 [cse7766] Use stack buffer for hex formatting in debug logging (#12732) 2026-01-02 14:20:17 -10:00
J. Nick Koston
0e108c2178 [esp32] Add minimum_chip_revision setting and log chip revision at startup (#12696) 2026-01-02 14:14:52 -10:00
J. Nick Koston
2230e56347 [wifi] Use stack buffers for IP address logging to avoid heap allocations (#12680) 2026-01-02 14:14:24 -10:00
J. Nick Koston
2ff9535f5f [esp32_improv] Use stack buffer for URL formatting to avoid heap allocation (#12682) 2026-01-02 14:14:12 -10:00
J. Nick Koston
ddb6c6cfd4 [captive_portal] Use stack buffer for IP address logging in DNS server (#12679) 2026-01-02 14:13:59 -10:00
J. Nick Koston
00ab64a3c7 [wifi] Use wifi_ssid_to() to avoid heap allocations in automation and connection checks (#12678) 2026-01-02 14:13:43 -10:00
J. Nick Koston
e732f8469e [udp] Avoid heap allocations when joining multicast groups (#12685) 2026-01-02 14:13:26 -10:00
J. Nick Koston
023be88a87 [tuya] Use stack buffers for hex logging to avoid heap allocations (#12689) 2026-01-02 14:13:08 -10:00
J. Nick Koston
25e60d62cf [mqtt] Avoid heap allocations when logging IP addresses (#12686) 2026-01-02 14:12:04 -10:00
J. Nick Koston
167a42aa27 [api] Use StringRef in send_action_response and send_execute_service_response (#12658) 2026-01-02 14:11:45 -10:00
J. Nick Koston
0ef49a8b73 [ld2410][ld2412][ld2450] Use stack buffers for hex logging (#12688) 2026-01-02 14:11:31 -10:00
J. Nick Koston
51259888bf [voice_assistant] Use zero-copy buffer access for audio data (#12656) 2026-01-02 14:10:21 -10:00
J. Nick Koston
0b7ff09657 [api] Use pointer to FixedVector for siren tones field (#12657) 2026-01-02 14:09:40 -10:00
J. Nick Koston
f394cf3f4d [packet_transport] Use stack-based format_hex_pretty_to for logging (#12791) 2026-01-02 14:06:03 -10:00
J. Nick Koston
4cb066bcbf [api] Use StringRef in handle_action_response to avoid temporary string (#12655) 2026-01-02 14:05:50 -10:00
J. Nick Koston
e7001c5eea [api] Auto-generate zero-copy pointer access for incoming API bytes fields (#12654) 2026-01-02 14:05:37 -10:00
esphomebot
5bb9ffa0cb Update webserver local assets to 20260102-230255 (#12817) 2026-01-02 23:14:11 +00:00
J. Nick Koston
c6713eaccb [web_server] Fix URL collisions with UTF-8 names and sub-devices (#12627) 2026-01-02 13:07:11 -10:00
Jonathan Swoboda
087f521b19 [ultrasonic] Use interrupt-based measurement for reliability (#12617)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-02 15:58:53 -05:00
Jonathan Swoboda
763515d3a1 [core] Remove unused USE_ESP32_FRAMEWORK_ARDUINO ifdefs (#12813)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-02 14:47:14 -05:00
J. Nick Koston
6d4f4d8d23 [api] Auto-generate StringRef for incoming API string fields (#12648) 2026-01-02 08:17:05 -10:00
Tobias Stanzel
d7fd85e610 [spi] Allow any achievable data rate (#12753)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-01-02 18:10:30 +11:00
J. Nick Koston
8acaa16987 [usb_cdc_acm] Use stack-based hex formatting in verbose logging (#12792) 2026-01-02 01:04:11 -06:00
J. Nick Koston
4e8c02b396 [xiaomi_*] Use stack-based hex formatting for bindkey logging (#12798) 2026-01-01 20:25:12 -10:00
J. Nick Koston
a828abf53d [ota] Remove MD5 authentication support (#12707) 2026-01-01 20:24:31 -10:00
J. Nick Koston
ebfa0149cc [light] Use StringRef to avoid allocation in JSON effect name serialization (#12758) 2026-01-01 20:23:37 -10:00
J. Nick Koston
3a4cca0027 [ble_client] Use stack buffer for hex formatting in very verbose logging (#12744) 2026-01-01 20:22:48 -10:00
J. Nick Koston
7702a9ae85 [ethernet] Use stack buffer for hex formatting in very verbose logging (#12742) 2026-01-01 20:22:19 -10:00
J. Nick Koston
2e8baa0493 [esp32_ble_tracker] Use stack buffer for hex formatting in very verbose logging (#12741) 2026-01-01 20:21:33 -10:00
J. Nick Koston
69ec311d21 [hlk_fm22x] Use stack buffer for hex formatting in verbose logging (#12740) 2026-01-01 20:20:58 -10:00
J. Nick Koston
1cc18055ef [i2c] Use stack buffer for hex formatting in verbose logging (#12739) 2026-01-01 20:20:24 -10:00
J. Nick Koston
bcc6bbbf5f [espnow] Use stack buffer for hex formatting in verbose logging (#12738) 2026-01-01 20:19:49 -10:00
J. Nick Koston
71c3d4ca27 [mopeka_std_check] Use stack-based format_hex_pretty_to for very verbose logging (#12790) 2026-01-01 20:19:20 -10:00
J. Nick Koston
c6f3860f90 [ee895] Use stack-based format_hex_to for verbose logging (#12789) 2026-01-01 20:18:23 -10:00
J. Nick Koston
0049c8ad38 [zwave_proxy] Use stack-based format_hex_pretty_to for very verbose logging (#12786) 2026-01-01 20:17:51 -10:00
J. Nick Koston
e1788bba45 [seeed_mr60fda2] Use stack-based format_hex_pretty_to for verbose logging (#12785) 2026-01-01 20:17:22 -10:00
J. Nick Koston
4fcd263ea8 [seeed_mr60bha2] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12784) 2026-01-01 20:16:40 -10:00
J. Nick Koston
c81ce243cc [qspi_dbi] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12783) 2026-01-01 20:13:10 -10:00
J. Nick Koston
7df41124b2 [pn532_spi] Replace format_hex_pretty with stack-based format_hex_pretty_to (#12782) 2026-01-01 20:11:53 -10:00
J. Nick Koston
b5188731f8 [modbus] Use stack buffer for hex formatting in verbose logging (#12780) 2026-01-01 20:10:45 -10:00
J. Nick Koston
0924281545 [mitsubishi] Use stack buffer for hex formatting in verbose logging (#12779) 2026-01-01 20:10:08 -10:00
J. Nick Koston
14e97642f7 [mipi_rgb] Use stack buffer for hex formatting in init sequence logging (#12777) 2026-01-01 20:09:37 -10:00
J. Nick Koston
544aaeaa66 [mipi_dsi] Use stack buffer for hex formatting in very verbose logging (#12776) 2026-01-01 20:08:57 -10:00
Stuart Parmenter
7483bbd6ea [display] Ensure drivers respect clipping during fill() (#12808) 2026-01-02 16:34:39 +11:00
Artur
2841b5fe44 [sn74hc595]: fix 'Attempted read from write-only channel' when using esp-idf framework (#12801) 2026-01-01 23:28:10 -05:00
J. Nick Koston
ed435241b1 [mipi_spi] Use stack buffer for hex formatting in verbose logging (#12778) 2026-01-01 11:48:37 -10:00
H. Árkosi Róbert
9847e51fbc [bthome_mithermometer] Add BTHome parsing for Xiaomi Mijia BLE Sensors (#12635) 2026-01-02 08:40:18 +11:00
dependabot[bot]
dc320f455a Bump bleak from 2.1.0 to 2.1.1 (#12804)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 09:16:01 -10:00
Clyde Stubbs
1945e85ddc [core] Make LockFreeQueue more widely available (#12766) 2026-01-01 22:07:35 +11:00
Clyde Stubbs
4313130f2e [lvgl] Fix arc background angles (#12773) 2026-01-01 14:44:21 +11:00
Jonathan Swoboda
3c9ed126a6 Merge branch 'release' into dev 2025-12-31 17:42:51 -05:00
Jonathan Swoboda
d8c23d4fc9 Merge pull request #12772 from esphome/bump-2025.12.4
2025.12.4
2025-12-31 17:42:39 -05:00
Konstantin Tretyakov
1d96de986e [sdist] Include yaml files in components in source distribution package
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-01-01 08:49:43 +11:00
Jonathan Swoboda
e9e0712959 Bump version to 2025.12.4 2025-12-31 16:07:00 -05:00
J. Nick Koston
062840dd7b [docker] Add build-essential to fix ruamel.yaml 0.19.0 compilation (#12769)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-31 16:07:00 -05:00
J. Nick Koston
f0f01c081a [wifi] Fix ESP-IDF reporting connected before DHCP completes on reconnect (#12755) 2025-12-31 16:07:00 -05:00
Stuart Parmenter
dd855985be [hub75] Add clipping check (#12762) 2025-12-31 16:06:59 -05:00
J. Nick Koston
4633803d5d [docker] Add build-essential to fix ruamel.yaml 0.19.0 compilation (#12769)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-31 16:05:58 -05:00
J. Nick Koston
476d00d0e5 [wifi] Fix ESP-IDF reporting connected before DHCP completes on reconnect (#12755) 2025-12-31 15:59:28 -05:00
Stuart Parmenter
98cdef2568 [hub75] Add clipping check (#12762) 2025-12-31 15:58:37 -05:00
J. Nick Koston
bd3ecad3a1 [core] Add format_hex_pretty_to buffer helper and reduce code duplication (#12687) 2025-12-30 11:51:51 -10:00
J. Nick Koston
dae7ba604a [ethernet_info] Eliminate heap allocations in DNS text sensor (#12756) 2025-12-30 10:25:51 -10:00
Jonathan Swoboda
96c47f3b4d Merge branch 'release' into dev 2025-12-30 09:31:44 -05:00
Samuel Sieb
a615b28ecf [bme68x_bsec2] add id: to allow extending (#12649) 2025-12-29 23:22:36 -08:00
bakroistvan
468bd7b04f [dallas_temp] higher precision for logged temperature (#12695) 2025-12-29 22:53:28 -08:00
Jonathan Swoboda
4c16afeacb [esp32] Add IDF framework source for Arduino builds (#12731)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-29 22:25:26 -05:00
J. Nick Koston
d86c05bfe6 [esp32] Breaking Change: Change default framework to ESP-IDF (#12746)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-30 03:23:41 +00:00
J. Nick Koston
63464a13c3 [core] Fix incremental build failures when adding components on ESP32-Arduino (#12745) 2025-12-29 16:57:22 -10:00
Clyde Stubbs
20e43398fa [cli] Report program path on host (#12743) 2025-12-30 13:21:30 +11:00
hsand
2e7cdad532 [pvvx_mithermometer] fix displaying negative numbers (#12735) 2025-12-29 13:58:38 -08:00
dependabot[bot]
636cccc6a3 Bump aioesphomeapi from 43.9.0 to 43.9.1 (#12724)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 09:55:26 -10:00
Thomas Rupprecht
93e2a1bd1a [tests] improve mipi_spi variable naming (#12716) 2025-12-29 14:21:07 -05:00
Thomas Rupprecht
dd3beb5841 [tests] fix typo mipi tests (#12715) 2025-12-29 14:20:38 -05:00
Thomas Rupprecht
97af01c5ed [usb_host] sort esp32 variants (#12720) 2025-12-29 14:19:36 -05:00
J. Nick Koston
7e362cdafc [ota] Use precision format specifier for auth logging (#12706)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-29 08:43:54 -10:00
Jonathan Swoboda
890d531cea [esp32] Bump to ESP-IDF 5.5.2, Arduino 3.3.5, platform 55.3.35 (#12681)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-29 11:35:54 -05:00
Swaptor
6a6c6b648f [internal_temperature] Add ESP32-C5 support (#12713)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-12-29 11:32:32 -05:00
dependabot[bot]
d0673122a8 Bump aioesphomeapi from 43.8.0 to 43.9.0 (#12702) 2025-12-28 18:15:06 -10:00
dependabot[bot]
5cbef3ef95 Bump aioesphomeapi from 43.7.0 to 43.8.0 (#12701) 2025-12-29 03:15:40 +00:00
dependabot[bot]
a1e0121330 Bump bleak from 2.0.0 to 2.1.0 (#12700)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 16:48:20 -10:00
dependabot[bot]
eb050ff13e Bump aioesphomeapi from 43.6.0 to 43.7.0 (#12699)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-28 16:48:08 -10:00
Jonathan Swoboda
45e61f100c [core] Replace USE_ESP_IDF with USE_ESP32 across components (#12673)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-27 11:59:55 -10:00
J. Nick Koston
5e99dd14ae [ethernet] Eliminate heap allocations in dump_config logging (#12665) 2025-12-27 08:36:35 -10:00
J. Nick Koston
a6097f4a0f [wifi] Eliminate heap allocations in dump_config logging (#12664)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-27 08:36:19 -10:00
J. Nick Koston
f243e609a5 [wifi] Use StringRef and std::span in WiFiConnectStateListener to avoid allocations (#12672) 2025-12-27 08:35:58 -10:00
J. Nick Koston
be0bf1e5b9 [lvgl] Fix lambdas in canvas actions called from outside LVGL context (#12671) 2025-12-27 08:35:36 -10:00
J. Nick Koston
a275f37135 [udp] Use stack buffer for listen address logging in dump_config (#12667) 2025-12-27 08:35:16 -10:00
J. Nick Koston
e9f2d75aab [core] Add format_hex_to helper for zero-allocation hex formatting (#12670) 2025-12-27 08:34:45 -10:00
J. Nick Koston
34067f8b15 [esp8266] Native OTA backend to reduce Arduino dependencies (#12675)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-27 08:29:15 -10:00
J. Nick Koston
bdc087148a [wifi_info] Reduce heap allocations in text sensor formatting (#12660) 2025-12-26 12:52:41 -10:00
J. Nick Koston
5a2e0612a8 [web_server] Use C++17 nested namespace syntax (#12663) 2025-12-26 08:44:34 -10:00
J. Nick Koston
f1fecd22e3 [web_server] Move HTTP header strings to flash on ESP8266 (#12668) 2025-12-26 08:44:17 -10:00
J. Nick Koston
0919017d49 [wifi] Avoid unnecessary string copy in failed connection logging (#12659) 2025-12-26 08:44:03 -10:00
J. Nick Koston
963f594c9e [text_sensor] Return state by const reference to avoid copies (#12661) 2025-12-26 07:58:46 -10:00
J. Nick Koston
4f70663658 [alarm_control_panel] Use C++17 nested namespace and remove unused include (#12662) 2025-12-26 07:57:33 -10:00
dependabot[bot]
958a35e262 Bump aioesphomeapi from 43.5.0 to 43.6.0 (#12644)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-24 14:17:52 -10:00
J. Nick Koston
0c566c6f00 [core] Deprecate get_object_id() and migrate remaining usages to get_object_id_to() (#12629) 2025-12-23 06:59:07 -10:00
Jonathan Swoboda
ba73289b28 Merge branch 'release' into dev 2025-12-23 11:17:15 -05:00
Jonathan Swoboda
ffefa8929e [cc1101] Fix packet mode RSSI/LQI (#12630)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 09:05:48 -05:00
J. Nick Koston
7d5342bca5 [logger] Host: Use fwrite() with explicit length and remove platform branching (#12628) 2025-12-22 16:45:22 -10:00
J. Nick Koston
b4c92dd8cb [logger] Zephyr: Use k_str_out() with known length instead of printk() (#12619) 2025-12-22 14:29:47 -10:00
eoasmxd
1b31253287 Add Event Component to UART (#11765)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-22 12:19:48 -10:00
J. Nick Koston
af0d4d2c2c [web_server] Use stack buffers for value formatting to reduce flash usage (#12575) 2025-12-22 21:56:07 +00:00
J. Nick Koston
f238f93312 [core] Move comment to PROGMEM on ESP8266 (#12554) 2025-12-22 21:37:51 +00:00
J. Nick Koston
bdbe72b7f1 [web_server] Make internal JSON helper methods private (#12624) 2025-12-22 11:14:11 -10:00
J. Nick Koston
c8b531ac06 [safe_mode] Defer preference sync in clean_rtc to avoid blocking event loop (#12625) 2025-12-22 11:13:51 -10:00
Jonathan Swoboda
918bc4b74f [esp32] Remove remaining using_esp_idf checks (#12623)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 15:41:14 -05:00
Keith Burzinski
08c0f65f30 [sprinkler] Remove internal latching valve support (#12603) 2025-12-22 14:13:18 -05:00
Keith Burzinski
cd45fe0c3a [thermostat] Optimizations to reduce binary size (#12621) 2025-12-22 14:13:03 -05:00
Jonathan Swoboda
84b5d9b21c [core] Remove deprecated config options from before 2025 (#12622)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 14:00:12 -05:00
J. Nick Koston
6383fe4598 [core] Add zero-allocation object_id methods (#12578) 2025-12-22 07:56:33 -10:00
J. Nick Koston
265ad9d264 [esp32_camera] Reduce loop overhead and improve frame latency with wake_loop_threadsafe (#12601) 2025-12-22 07:55:28 -10:00
J. Nick Koston
1bdbc4cb85 [esp32_ble] Avoid string allocation when setting BLE device name (#12579) 2025-12-22 07:54:55 -10:00
J. Nick Koston
1756fc31b0 [api] Use union for iterators to reduce APIConnection size by ~16 bytes (#12563) 2025-12-22 07:54:17 -10:00
J. Nick Koston
74b075d3cf [codegen] Add static storage class to global variables for size optimization (#12616) 2025-12-22 07:03:17 -10:00
Clint Armstrong
52eb08f48f [thermostat] Enhance timer behavior for immediate response to duration changes (#12610) 2025-12-21 23:52:17 -06:00
J. Nick Koston
0d993691d4 [logger] RP2040: Use write() with known length instead of println() (#12615) 2025-12-21 17:59:30 -10:00
Douwe
39926909af [water_heater] (1/4) Implement API/Core/component for new water_heater component (#12498)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-21 11:36:34 -10:00
J. Nick Koston
637e032528 [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) 2025-12-21 09:04:43 -10:00
Anna Oake
d89eaf5bf6 [cc1101] Fix option defaults and move them to YAML (#12608) 2025-12-21 13:04:17 -05:00
J. Nick Koston
bf617c3279 [web_server] Replace str_sprintf with stack buffers (#12592) 2025-12-21 07:32:05 -10:00
J. Nick Koston
c70eab931e [api] Add zero-copy support for Home Assistant state response messages (#12585) 2025-12-21 07:31:54 -10:00
J. Nick Koston
a799ac6488 [syslog] Eliminate heap allocations in log path (#12589) 2025-12-21 07:10:27 -10:00
polyfloyd
5a36cea5ec Add nix files to gitignore (#12604) 2025-12-21 09:26:03 -05:00
J. Nick Koston
60756db06d [syslog] Use C++17 nested namespace syntax (#12594) 2025-12-21 02:47:37 -06:00
Keith Burzinski
2113858f89 [sprinkler] Squash a few bugs + minor optimization (#12436) 2025-12-21 02:45:24 -06:00
dependabot[bot]
e89fe9b945 Bump aioesphomeapi from 43.4.0 to 43.5.0 (#12599)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-20 23:59:48 +00:00
dependabot[bot]
f1362cd9fe Bump aioesphomeapi from 43.3.0 to 43.4.0 (#12597)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-20 22:37:10 +00:00
Frédéric Metrich
bf554a58ef [const] Add CONF_ON_DATA and consolidate definitions (#12595)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 12:17:09 -10:00
J. Nick Koston
644e806afd [zwave_proxy] Add missing USE_API guards for clang-tidy (#12590) 2025-12-20 10:40:43 -10:00
Leo Bergolth
6c2d255230 send NIL ("-") as timestamp if time source is not valid (#12588) 2025-12-20 10:04:59 -10:00
Stuart Parmenter
6f3bfc2060 [hub75] Bump esp-hub75 version to 0.1.7 (#12564) 2025-12-20 13:18:20 -05:00
J. Nick Koston
40eb898814 [api] Add zero-copy support for noise encryption key requests (#12405) 2025-12-20 06:47:30 -10:00
J. Nick Koston
64269334ce [text_sensor] Avoid string copies in callbacks by passing const ref (#12503)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-20 06:46:13 -10:00
Eduard Llull
121375ff39 [display_menu_base] Call on_value_ after updating the select (#12584) 2025-12-20 10:59:14 -05:00
J. Nick Koston
48cdf9e036 [tests] Fix race condition in alarm control panel state transitions test (#12581) 2025-12-20 10:47:29 -05:00
J. Nick Koston
3e313014e1 [core] Migrate entities to use lazy callbacks (#12580) 2025-12-19 19:04:21 -10:00
Martin Ebner
be6c1e4ec0 [sen5x][sgp4x] Move configuration keys from SEN5x and SGP4x to const.py (#12567)
Co-authored-by: Martin Ebner <martinebner@me.com>
2025-12-19 21:29:02 -05:00
Keith Burzinski
730bf206de [wifi] Fix for wifi_info when static IP is configured (#12576) 2025-12-19 21:25:16 -05:00
J. Nick Koston
c9fccdff25 [fan] Add zero-copy support for API preset mode commands (#12404)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-19 22:05:52 +00:00
J. Nick Koston
ada6c42f3f [alarm_control_panel] Remove redundant per-state callbacks (#12171)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-19 11:48:14 -10:00
J. Nick Koston
988b888c63 [ota] Replace std::function callbacks with listener interface (#12167) 2025-12-19 11:19:07 -10:00
J. Nick Koston
940afdbb12 [climate] Add zero-copy support for API custom fan mode and preset commands (#12402) 2025-12-19 11:18:50 -10:00
J. Nick Koston
81e91c2a8f [esp32_ble] Add stack-based UUID formatting to avoid heap allocations (#12510) 2025-12-19 11:18:32 -10:00
J. Nick Koston
ebc3d28ade [wifi] Replace optional with sentinel values to reduce RAM and clarify API (#12446) 2025-12-19 11:18:15 -10:00
Rene Guca
25cebedcfc [dht] Fix "Falling edge for bit 39 failed!" for Sonoff THS01 (#9225)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-19 15:42:39 -05:00
dependabot[bot]
98ed679b19 Bump ruff from 0.14.9 to 0.14.10 (#12572)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-19 19:22:56 +00:00
dependabot[bot]
59b38d79b4 Bump docker/setup-buildx-action from 3.11.1 to 3.12.0 in the docker-actions group (#12574)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 09:18:52 -10:00
dependabot[bot]
26c16f4ca2 Bump voluptuous from 0.15.2 to 0.16.0 (#12573)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 09:18:33 -10:00
Jas Strong
940e619481 [aqi, hm3301, pmsx003] Air Quality Index improvements (#12203)
Co-authored-by: jas <jas@asspa.in>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-12-19 13:42:11 -05:00
Jonathan Swoboda
eaca81c3ab Merge branch 'release' into dev 2025-12-19 10:53:18 -05:00
pixelgrb
f962497db1 [mmc5603] enable AUTO_SR_en to compensate for temperature drift (#12556)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-18 23:13:36 -05:00
Keith Burzinski
7ae3a11d6b [esp32_ble, esp32_ble_tracker] Fix crash, error messages when ble.disable called during boot (#12560) 2025-12-18 19:42:47 -06:00
dependabot[bot]
1c50c2b672 Bump ruamel-yaml from 0.18.16 to 0.18.17 (#12555)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 11:19:19 -10:00
Jonathan Swoboda
41fd1762e9 [core] Deprecate custom_components folder (#12552)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 11:46:16 -05:00
J. Nick Koston
2cf6ed2af7 [socket] Refactor socket implementations for memory efficiency and code quality (#12550) 2025-12-18 09:07:35 -07:00
J. Nick Koston
b47b7d43fd [api] Remove unused force parameter from encode_message (#12551) 2025-12-18 09:06:16 -07:00
Jonathan Swoboda
663a4304e0 [libretiny] Fix millis() ambiguity on BK72XX (#12534)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 07:50:31 -05:00
Jonathan Swoboda
ca47bad90a [template.alarm_control_panel] Fix compile without binary_sensor (#12548)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 23:34:04 -05:00
J. Nick Koston
4f821a6d76 [wifi] Reduce scan logging to prevent blocking loop during connection (#12544) 2025-12-17 18:21:46 -10:00
J. Nick Koston
426305836d [esp32][libretiny] Avoid duplicate snprintf when syncing preferences (#12542) 2025-12-17 18:16:14 -10:00
dependabot[bot]
1b5af7d21d Bump github/codeql-action from 4.31.8 to 4.31.9 (#12524)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 15:22:19 -10:00
David Woodhouse
9de7df7b5b Add build info to image (#12425)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-18 00:06:52 +00:00
Jonathan Swoboda
2b337aa306 [esp32_camera] Fix I2C driver conflict with other components (#12533)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:37:59 -05:00
Jonathan Swoboda
4ddaff4027 [esp32] Dynamically embed managed component server certificates (#12509)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:26:56 -05:00
J. Nick Koston
91c504061b [select] Eliminate string allocation in state callbacks (#12505)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 12:19:26 -10:00
Jonathan Swoboda
dc8f7abce2 [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 17:07:42 -05:00
Jonathan Swoboda
3d673ac55e [ci] Check changed headers in clang-tidy when using --changed (#12540)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 11:13:18 -10:00
Jack Wilsdon
b02696edc0 [pm1006] Fix "never" update interval detection (#12529) 2025-12-18 07:40:31 +11:00
Anna Oake
f9720026d0 [cc1101] Fix default frequencies (#12539) 2025-12-17 14:19:18 -05:00
Jonathan Swoboda
d7b04a3d18 [nextion] Fix clang-tidy error on Zephyr for HTTPClient (#12538)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 13:59:49 -05:00
Jonathan Swoboda
0e71fa97a7 [spi] Add SPIInterface stub for clang-tidy on unsupported platforms (#12532)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 12:18:25 -05:00
J. Nick Koston
42e061c9ae [text] Avoid string copies in callbacks by passing const ref (#12504) 2025-12-17 12:00:19 -05:00
J. Nick Koston
94763ebdab [libretiny] Store preference keys as uint32_t, convert to string only at FlashDB boundary (#12500) 2025-12-17 11:59:40 -05:00
J. Nick Koston
f32bb618ac [esp32] Store preference keys as uint32_t, convert to string only at NVS boundary (#12494) 2025-12-17 11:59:35 -05:00
Edward Firmo
0707f383a6 [nextion] Use ESP-IDF for ESP32 Arduino (#9429)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:17 -05:00
Piotr Szulc
e91c6a79ea [deep_sleep] Deep sleep for BK72xx (#12267)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:05 -05:00
J. Nick Koston
63fc8b4e5a [core] Refactor str_snake_case and str_sanitize to use constexpr helpers (#12454) 2025-12-17 11:30:12 -05:00
J. Nick Koston
ab73ed76b8 [esphome] Improve OTA field alignment to save 4 bytes on 32-bit (#12461) 2025-12-17 11:29:58 -05:00
J. Nick Koston
bf6a03d1cf [factory_reset] Optimize memory by storing interval as uint16_t seconds (#12462) 2025-12-17 11:29:51 -05:00
J. Nick Koston
9928ab09cf [time] Convert to C++17 nested namespace syntax (#12463) 2025-12-17 11:29:43 -05:00
Thomas Rupprecht
56c1691d72 [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 22:52:28 -05:00
Roger Fachini
a065990ab9 [update] Add check action to trigger update checks (#12415) 2025-12-16 22:20:12 -05:00
Stuart Parmenter
084f517a20 [hub75] Add set_brightness action (#12521) 2025-12-16 22:12:33 -05:00
Jonathan Swoboda
1122ec354f [esp32] Add OTA rollback support (#12460)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:57 -05:00
Jonathan Swoboda
431183eebc [ledc,mqtt,resampler] Remove unnecessary ESP-IDF framework restrictions (#12442)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:09 -05:00
Jonathan Swoboda
08beaf8750 [esp32] Remove Arduino-specific code from core.cpp (#12501)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:06:12 -05:00
Jonathan Swoboda
18814f12dc [http_request] Use ESP-IDF for ESP32 Arduino (#12428)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:14 -05:00
Jonathan Swoboda
9cd888cef6 [spi] Use ESP-IDF driver for ESP32 Arduino (#12420)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:01 -05:00
Thomas Rupprecht
9727c7135c [openthread] channel range, fix typo, use C++17 nested namespace syntax (#12422) 2025-12-16 19:43:18 -05:00
Thomas Rupprecht
93621d85b0 [climate] Improve temperature unit regex (#12032) 2025-12-16 19:43:10 -05:00
Thomas Rupprecht
046ea922e8 [esp32] improve types and variable naming (#12423) 2025-12-16 19:42:52 -05:00
Jeff Zigler
fab4efb469 [esp32] Fix serial logging on h2, c2 & c61 (#12522)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 19:42:12 -05:00
Jonathan Swoboda
efc5672567 Merge branch 'release' into dev 2025-12-16 18:57:37 -05:00
Jonathan Swoboda
5e630e9255 Merge branch 'beta' into dev 2025-12-16 11:26:08 -05:00
Jonathan Swoboda
1897551b28 [uart] Fix UART on default UART0 pins for ESP-IDF (#12519)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:17:17 -05:00
J. Nick Koston
ead60bc5c4 [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) 2025-12-16 00:48:30 -06:00
Jonathan Swoboda
7fe8e53f82 Merge branch 'beta' into dev 2025-12-15 19:01:12 -05:00
David Woodhouse
839139df36 Add FNV-1a hash functions (#12502) 2025-12-15 20:23:54 +00:00
dependabot[bot]
24d7e9dd23 Bump tornado from 6.5.3 to 6.5.4 (#12508)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:08:16 +00:00
dependabot[bot]
1214bb6bad Bump aioesphomeapi from 43.2.1 to 43.3.0 (#12507)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:07:20 +00:00
Pascal Vizeli
260ffba2a5 [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 12:54:12 -05:00
Jonathan Swoboda
2e899dd010 [esp32] Support all IDF component version operators in shorthand syntax (#12499)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 12:07:02 -05:00
David Woodhouse
61cbd07e1d Add hmac-sha256 support (#12437)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-15 10:55:03 -06:00
Jonathan Swoboda
450962850a [remote_base] Fix crash when ABBWelcome action has no data field (#12493)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 09:29:51 -05:00
Jonathan Swoboda
6b088caf5d Merge branch 'beta' into dev 2025-12-14 19:18:10 -05:00
J. Nick Koston
ffce80f96c [wifi] Fix WiFi recovery after failed connection attempts (#12483) 2025-12-14 16:26:34 -06:00
mbohdal
fa5b14fad4 [ethernet] fix used pins validation in configuration of RMII pins (#12486) 2025-12-14 16:40:08 -05:00
guillempages
cee532a1e3 [core] Use Arduino string macros only on ESP8266 (#12471) 2025-12-15 07:15:19 +11:00
J. Nick Koston
8524b894d6 [ota] Match client timeout to device timeout to prevent premature failures (#12484) 2025-12-14 13:47:11 -06:00
J. Nick Koston
3a5e708c13 [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) 2025-12-14 13:31:19 -06:00
J. Nick Koston
96e418a8ca [wifi_signal] Skip publishing disconnected RSSI value (#12482) 2025-12-14 13:31:07 -06:00
J. Nick Koston
780a407b10 [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) 2025-12-14 13:30:55 -06:00
Jonathan Swoboda
cfc0d8bdfc [cc1101] Add packet mode support (#12474)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 13:22:55 -05:00
Jonathan Swoboda
786d7266f5 [core] Fix polling_component_schema and type consistency (#12478)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 12:47:52 -05:00
Clyde Stubbs
ede64a9f47 [packet_transport] Ensure retransmission at update intervals (#12472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-14 12:47:15 -05:00
J. Nick Koston
e0ce66e011 [core] Fix CORE.raw_config not updated after package merge (#12456) 2025-12-13 07:38:31 -06:00
David Woodhouse
6fce0a6104 Add host platform support to MD5 component (#12458) 2025-12-13 02:50:34 +00:00
David Woodhouse
ff7651875e Add HMAC-MD5 component tests (#12459) 2025-12-12 19:19:31 -06:00
David Woodhouse
1a43a06dd4 Add USE_SHA256 define to sha256 component to enable tests (#12457) 2025-12-12 19:15:50 -06:00
dependabot[bot]
51b187954a Bump ruff from 0.14.8 to 0.14.9 (#12448)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-12 19:20:06 +00:00
dependabot[bot]
9126b32c35 Bump actions/cache from 4.3.0 to 5.0.1 in /.github/actions/restore-python (#12453)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:17:08 -06:00
dependabot[bot]
4993bb2f49 Bump github/codeql-action from 4.31.7 to 4.31.8 (#12451)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:16:41 -06:00
dependabot[bot]
2b40af3459 Bump actions/cache from 4.3.0 to 5.0.1 (#12450)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:16:29 -06:00
dependabot[bot]
b3e967a233 Bump actions/download-artifact from 6.0.0 to 7.0.0 (#12449)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:15:41 -06:00
dependabot[bot]
26a08e3ae3 Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#12452)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:15:28 -06:00
Jonathan Swoboda
64d650c65c Merge branch 'beta' into dev 2025-12-12 12:15:52 -05:00
Jonathan Swoboda
d30d8156c1 [http_request] Skip update check when network not connected (#12418)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 10:31:17 -05:00
dependabot[bot]
8d1e68c4c1 Bump tornado from 6.5.2 to 6.5.3 (#12430)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 17:53:12 -06:00
J. Nick Koston
74218bc742 [api] Release prologue memory after noise handshake completes (#12412) 2025-12-10 19:33:22 -06:00
J. Nick Koston
369cc70fdf [climate] Save 48 bytes per entity by conditionally compiling visual overrides (#12406) 2025-12-10 19:10:42 -06:00
dependabot[bot]
1f0a27b181 Bump codecov/codecov-action from 5.5.1 to 5.5.2 (#12408)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 22:34:24 +01:00
dependabot[bot]
22918d3bd5 Bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12409)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 22:21:29 +01:00
J. Nick Koston
7a9fce90cb [text] Add integration tests for text command API (#12401) 2025-12-10 12:13:40 -05:00
dependabot[bot]
d1d376ebc8 Bump actions/create-github-app-token from 2.2.0 to 2.2.1 (#12370)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 13:05:01 +01:00
J. Nick Koston
c124d72ea9 [esp8266] Eliminate up to 16ms socket latency (#12397) 2025-12-10 03:45:27 +00:00
J. Nick Koston
567e82cfec [api] Fix potential buffer overflow in noise PSK base64 decode (#12395) 2025-12-10 04:20:23 +01:00
J. Nick Koston
b1f9100b02 [core] Add constexpr parse_hex_char helper and simplify parse_hex (#12394) 2025-12-10 04:20:08 +01:00
J. Nick Koston
d0fbc82f47 [esp32_ble_client] Use stack-based MAC formatting in auth logging (#12393)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 04:19:52 +01:00
J. Nick Koston
03c391bd43 [light] Add zero-copy support for API effect commands (#12384) 2025-12-10 04:19:29 +01:00
Jonathan Swoboda
5601a2b686 Merge branch 'beta' into dev 2025-12-09 21:34:12 -05:00
Jonathan Swoboda
84d5348bd8 Bump version to 2026.1.0-dev 2025-12-09 20:08:35 -05:00
J. Nick Koston
41ac12a0e1 Update esphome/components/bh1750/bh1750.cpp 2025-11-17 08:48:42 -06:00
J. Nick Koston
a6f416a09e Update esphome/components/bh1750/bh1750.cpp 2025-11-17 08:48:13 -06:00
J. Nick Koston
78a69cb744 tidy 2025-11-16 22:33:59 -06:00
J. Nick Koston
9b14444dad tidy 2025-11-16 22:30:59 -06:00
J. Nick Koston
8934d4b498 touch ups 2025-11-16 22:08:44 -06:00
J. Nick Koston
9b107e7f2a touch ups 2025-11-16 22:05:33 -06:00
884 changed files with 26615 additions and 12513 deletions

View File

@@ -1 +1 @@
5969e705693278d984c5292e998df0cbaf34f7e1f04dfc7f7b7ad7168527bfa7
191a0e6ab5842d153dd77a2023bc5742f9d4333c334de8d81b57f2b8d4d4b65e

View File

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

View File

@@ -26,7 +26,7 @@ jobs:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}

View File

@@ -62,7 +62,7 @@ jobs:
run: git diff
- if: failure()
name: Archive artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: generated-proto-files
path: |

View File

@@ -49,7 +49,7 @@ jobs:
with:
python-version: "3.11"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Set TAG
run: |

View File

@@ -47,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
# yamllint disable-line rule:line-length
@@ -152,12 +152,12 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
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 }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Restore components graph cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: .temp/components_graph.json
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
- name: Save components graph cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -245,7 +245,7 @@ jobs:
python-version: "3.13"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -334,14 +334,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
@@ -413,14 +413,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -502,14 +502,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -735,7 +735,7 @@ jobs:
- name: Restore cached memory analysis
id: cache-memory-analysis
if: steps.check-script.outputs.skip != 'true'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }}
@@ -759,7 +759,7 @@ jobs:
- name: Cache platformio
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
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
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }}
@@ -821,7 +821,7 @@ jobs:
fi
- name: Upload memory analysis JSON
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: memory-analysis-target
path: memory-analysis-target.json
@@ -847,7 +847,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
@@ -885,7 +885,7 @@ jobs:
--platform "$platform"
- name: Upload memory analysis JSON
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: memory-analysis-pr
path: memory-analysis-pr.json
@@ -915,13 +915,13 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Download target analysis JSON
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: memory-analysis-target
path: ./memory-analysis
continue-on-error: true
- name: Download PR analysis JSON
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: memory-analysis-pr
path: ./memory-analysis

View File

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

View File

@@ -99,7 +99,7 @@ jobs:
python-version: "3.11"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to docker hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
@@ -138,7 +138,7 @@ jobs:
# version: ${{ needs.init.outputs.tag }}
- name: Upload digests
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: digests-${{ matrix.platform.arch }}
path: /tmp/digests
@@ -171,14 +171,14 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Download digests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: digests-*
path: /tmp/digests
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
@@ -221,7 +221,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -256,7 +256,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -287,7 +287,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}

View File

@@ -41,7 +41,7 @@ jobs:
python script/run-in-env.py pre-commit run --all-files
- name: Commit changes
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org>

4
.gitignore vendored
View File

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

View File

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

View File

@@ -42,6 +42,7 @@ esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah
esphome/components/api/* @esphome/core
esphome/components/aqi/* @freekode @jasstrong @ximex
esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze
esphome/components/as7341/* @mrgnr
@@ -90,6 +91,7 @@ esphome/components/bmp3xx_spi/* @latonita
esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/bthome_mithermometer/* @nagyrobi
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @bdraco @DT-art1
@@ -133,7 +135,7 @@ esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/touchscreen/* @jesserockz
@@ -215,6 +217,7 @@ esphome/components/hlk_fm22x/* @OnFreund
esphome/components/hlw8032/* @rici4kubicek
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
@@ -392,6 +395,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
esphome/components/rd03d/* @jasstrong
esphome/components/resampler/speaker/* @kahrendt
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
@@ -517,6 +521,7 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/uart/event/* @eoasmxd
esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
@@ -535,6 +540,7 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz @kahrendt
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/water_heater/* @dhoeben
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server/ota/* @esphome/core
esphome/components/web_server_base/* @esphome/core
@@ -570,5 +576,6 @@ esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zigbee/* @tomaszduda23
esphome/components/zio_ultrasonic/* @kahrendt
esphome/components/zwave_proxy/* @kbx81

View File

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

View File

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

View File

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

View File

@@ -62,6 +62,9 @@ from esphome.util import (
_LOGGER = logging.getLogger(__name__)
# Maximum buffer size for serial log reading to prevent unbounded memory growth
SERIAL_BUFFER_MAX_SIZE = 65536
# Special non-component keys that appear in configs
_NON_COMPONENT_KEYS = frozenset(
{
@@ -431,25 +434,37 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
while tries < 5:
try:
with ser:
buffer = b""
ser.timeout = 0.1 # 100ms timeout for non-blocking reads
while True:
try:
raw = ser.readline()
# Read all available data and timestamp it
chunk = ser.read(ser.in_waiting or 1)
if not chunk:
continue
time_ = datetime.now()
milliseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{milliseconds:03}]"
# Add to buffer and process complete lines
# Limit buffer size to prevent unbounded memory growth
# if device sends data without newlines
buffer += chunk
if len(buffer) > SERIAL_BUFFER_MAX_SIZE:
buffer = buffer[-SERIAL_BUFFER_MAX_SIZE:]
while b"\n" in buffer:
raw_line, buffer = buffer.split(b"\n", 1)
line = raw_line.replace(b"\r", b"").decode(
"utf8", "backslashreplace"
)
safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
)
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return 0
line = (
raw.replace(b"\r", b"")
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
time_ = datetime.now()
nanoseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
)
except serial.SerialException:
tries += 1
time.sleep(1)
@@ -518,10 +533,49 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
rc = platformio_api.run_compile(config, CORE.verbose)
if rc != 0:
return rc
# Check if firmware was rebuilt and emit build_info + create manifest
_check_and_emit_build_info()
idedata = platformio_api.get_idedata(config)
return 0 if idedata is not None else 1
def _check_and_emit_build_info() -> None:
"""Check if firmware was rebuilt and emit build_info."""
import json
firmware_path = CORE.firmware_bin
build_info_json_path = CORE.relative_build_path("build_info.json")
# Check if both files exist
if not firmware_path.exists() or not build_info_json_path.exists():
return
# Check if firmware is newer than build_info (indicating a relink occurred)
if firmware_path.stat().st_mtime <= build_info_json_path.stat().st_mtime:
return
# Read build_info from JSON
try:
with open(build_info_json_path, encoding="utf-8") as f:
build_info = json.load(f)
except (OSError, json.JSONDecodeError) as e:
_LOGGER.debug("Failed to read build_info: %s", e)
return
config_hash = build_info.get("config_hash")
build_time_str = build_info.get("build_time_str")
if config_hash is None or build_time_str is None:
return
# Emit build_info with human-readable time
_LOGGER.info(
"Build Info: config_hash=0x%08x build_time_str=%s", config_hash, build_time_str
)
def upload_using_esptool(
config: ConfigType, port: str, file: str, speed: int
) -> str | int:
@@ -750,7 +804,13 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
_LOGGER.info("Successfully compiled program.")
if CORE.is_host:
from esphome.platformio_api import get_idedata
program_path = str(get_idedata(config).firmware_elf_path)
_LOGGER.info("Successfully compiled program to path '%s'", program_path)
else:
_LOGGER.info("Successfully compiled program.")
return 0
@@ -800,10 +860,8 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
if CORE.is_host:
from esphome.platformio_api import get_idedata
idedata = get_idedata(config)
if idedata is None:
return 1
program_path = idedata.raw["prog_path"]
program_path = str(get_idedata(config).firmware_elf_path)
_LOGGER.info("Running program from path '%s'", program_path)
return run_external_process(program_path)
# Get devices, resolving special identifiers like OTA
@@ -974,6 +1032,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
idedata.objdump_path,
idedata.readelf_path,
external_components,
idedata=idedata,
)
analyzer.analyze()

View File

@@ -22,6 +22,7 @@ from .helpers import (
map_section_name,
parse_symbol_line,
)
from .toolchain import find_tool, run_tool
if TYPE_CHECKING:
from esphome.platformio_api import IDEData
@@ -53,6 +54,9 @@ _NAMESPACE_STD = "std::"
# Type alias for symbol information: (symbol_name, size, component)
SymbolInfoType = tuple[str, int, str]
# RAM sections - symbols in these sections consume RAM
RAM_SECTIONS = frozenset([".data", ".bss"])
@dataclass
class MemorySection:
@@ -60,7 +64,20 @@ class MemorySection:
name: str
symbols: list[SymbolInfoType] = field(default_factory=list)
total_size: int = 0
total_size: int = 0 # Actual section size from ELF headers
symbol_size: int = 0 # Sum of symbol sizes (may be less than total_size)
@dataclass
class SDKSymbol:
"""Represents a symbol from an SDK library that's not in the ELF symbol table."""
name: str
size: int
library: str # Name of the .a file (e.g., "libpp.a")
section: str # ".bss" or ".data"
is_local: bool # True if static/local symbol (lowercase in nm output)
demangled: str = "" # Demangled name (populated after analysis)
@dataclass
@@ -118,6 +135,10 @@ class MemoryAnalyzer:
self.objdump_path = objdump_path or "objdump"
self.readelf_path = readelf_path or "readelf"
self.external_components = external_components or set()
self._idedata = idedata
# Derive nm path from objdump path using shared toolchain utility
self.nm_path = find_tool("nm", self.objdump_path)
self.sections: dict[str, MemorySection] = {}
self.components: dict[str, ComponentMemory] = defaultdict(
@@ -128,15 +149,25 @@ class MemoryAnalyzer:
self._esphome_core_symbols: list[
tuple[str, str, int]
] = [] # Track core symbols
self._component_symbols: dict[str, list[tuple[str, str, int]]] = defaultdict(
# Track symbols for all components: (symbol_name, demangled, size, section)
self._component_symbols: dict[str, list[tuple[str, str, int, str]]] = (
defaultdict(list)
)
# Track RAM symbols separately for detailed analysis: (symbol_name, demangled, size, section)
self._ram_symbols: dict[str, list[tuple[str, str, int, str]]] = defaultdict(
list
) # Track symbols for all components
)
# Track ELF symbol names for SDK cross-reference
self._elf_symbol_names: set[str] = set()
# SDK symbols not in ELF (static/local symbols from closed-source libs)
self._sdk_symbols: list[SDKSymbol] = []
def analyze(self) -> dict[str, ComponentMemory]:
"""Analyze the ELF file and return component memory usage."""
self._parse_sections()
self._parse_symbols()
self._categorize_symbols()
self._analyze_sdk_libraries()
return dict(self.components)
def _parse_sections(self) -> None:
@@ -190,6 +221,8 @@ class MemoryAnalyzer:
continue
self.sections[section].symbols.append((name, size, ""))
self.sections[section].symbol_size += size
self._elf_symbol_names.add(name)
seen_addresses.add(address)
def _categorize_symbols(self) -> None:
@@ -233,8 +266,13 @@ class MemoryAnalyzer:
if size > 0:
demangled = self._demangle_symbol(symbol_name)
self._component_symbols[component].append(
(symbol_name, demangled, size)
(symbol_name, demangled, size, section_name)
)
# Track RAM symbols separately for detailed RAM analysis
if section_name in RAM_SECTIONS:
self._ram_symbols[component].append(
(symbol_name, demangled, size, section_name)
)
def _identify_component(self, symbol_name: str) -> str:
"""Identify which component a symbol belongs to."""
@@ -328,6 +366,247 @@ class MemoryAnalyzer:
return "Other Core"
def get_unattributed_ram(self) -> tuple[int, int, int]:
"""Get unattributed RAM sizes (SDK/framework overhead).
Returns:
Tuple of (unattributed_bss, unattributed_data, total_unattributed)
These are bytes in RAM sections that have no corresponding symbols.
"""
bss_section = self.sections.get(".bss")
data_section = self.sections.get(".data")
unattributed_bss = 0
unattributed_data = 0
if bss_section:
unattributed_bss = max(0, bss_section.total_size - bss_section.symbol_size)
if data_section:
unattributed_data = max(
0, data_section.total_size - data_section.symbol_size
)
return unattributed_bss, unattributed_data, unattributed_bss + unattributed_data
def _find_sdk_library_dirs(self) -> list[Path]:
"""Find SDK library directories based on platform.
Returns:
List of paths to SDK library directories containing .a files.
"""
sdk_dirs: list[Path] = []
if self._idedata is None:
return sdk_dirs
# Get the CC path to determine the framework location
cc_path = getattr(self._idedata, "cc_path", None)
if not cc_path:
return sdk_dirs
cc_path = Path(cc_path)
# For ESP8266 Arduino framework
# CC is like: ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc
# SDK libs are in: ~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/
if "xtensa-lx106" in str(cc_path):
platformio_dir = cc_path.parent.parent.parent
esp8266_sdk = (
platformio_dir
/ "framework-arduinoespressif8266"
/ "tools"
/ "sdk"
/ "lib"
)
if esp8266_sdk.exists():
sdk_dirs.append(esp8266_sdk)
# Also check for NONOSDK subdirectories (closed-source libs)
sdk_dirs.extend(
subdir
for subdir in esp8266_sdk.iterdir()
if subdir.is_dir() and subdir.name.startswith("NONOSDK")
)
# For ESP32 IDF framework
# CC is like: ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-gcc
# or: ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc
elif "xtensa-esp" in str(cc_path) or "riscv32-esp" in str(cc_path):
# Detect ESP32 variant from CC path or defines
variant = self._detect_esp32_variant()
if variant:
platformio_dir = cc_path.parent.parent.parent
espidf_dir = platformio_dir / "framework-espidf" / "components"
if espidf_dir.exists():
# Find all directories named after the variant that contain .a files
# This handles various ESP-IDF library layouts:
# - components/*/lib/<variant>/
# - components/*/<variant>/
# - components/*/lib/lib/<variant>/
# - components/*/*/lib_*/<variant>/
sdk_dirs.extend(
variant_dir
for variant_dir in espidf_dir.rglob(variant)
if variant_dir.is_dir() and any(variant_dir.glob("*.a"))
)
return sdk_dirs
def _detect_esp32_variant(self) -> str | None:
"""Detect ESP32 variant from idedata defines.
Returns:
Variant string like 'esp32', 'esp32s2', 'esp32c3', etc. or None.
"""
if self._idedata is None:
return None
defines = getattr(self._idedata, "defines", [])
if not defines:
return None
# ESPHome always adds USE_ESP32_VARIANT_xxx defines
variant_prefix = "USE_ESP32_VARIANT_"
for define in defines:
if define.startswith(variant_prefix):
# Extract variant name and convert to lowercase
# USE_ESP32_VARIANT_ESP32 -> esp32
# USE_ESP32_VARIANT_ESP32S3 -> esp32s3
return define[len(variant_prefix) :].lower()
return None
def _parse_sdk_library(
self, lib_path: Path
) -> tuple[list[tuple[str, int, str, bool]], set[str]]:
"""Parse a single SDK library for symbols.
Args:
lib_path: Path to the .a library file
Returns:
Tuple of:
- List of BSS/DATA symbols: (symbol_name, size, section, is_local)
- Set of global BSS/DATA symbol names (for checking if RAM is linked)
"""
ram_symbols: list[tuple[str, int, str, bool]] = []
global_ram_symbols: set[str] = set()
result = run_tool([self.nm_path, "--size-sort", str(lib_path)], timeout=10)
if result is None:
return ram_symbols, global_ram_symbols
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) < 3:
continue
try:
size = int(parts[0], 16)
sym_type = parts[1]
name = parts[2]
# Only collect BSS (b/B) and DATA (d/D) for RAM analysis
if sym_type in ("b", "B"):
section = ".bss"
is_local = sym_type == "b"
ram_symbols.append((name, size, section, is_local))
# Track global RAM symbols (B/D) for linking check
if sym_type == "B":
global_ram_symbols.add(name)
elif sym_type in ("d", "D"):
section = ".data"
is_local = sym_type == "d"
ram_symbols.append((name, size, section, is_local))
if sym_type == "D":
global_ram_symbols.add(name)
except (ValueError, IndexError):
continue
return ram_symbols, global_ram_symbols
def _analyze_sdk_libraries(self) -> None:
"""Analyze SDK libraries to find symbols not in the ELF.
This finds static/local symbols from closed-source SDK libraries
that consume RAM but don't appear in the final ELF symbol table.
Only includes symbols from libraries that have RAM actually linked
(at least one global BSS/DATA symbol in the ELF).
"""
sdk_dirs = self._find_sdk_library_dirs()
if not sdk_dirs:
_LOGGER.debug("No SDK library directories found")
return
_LOGGER.debug("Analyzing SDK libraries in %d directories", len(sdk_dirs))
# Track seen symbols to avoid duplicates from multiple SDK versions
seen_symbols: set[str] = set()
for sdk_dir in sdk_dirs:
for lib_path in sorted(sdk_dir.glob("*.a")):
lib_name = lib_path.name
ram_symbols, global_ram_symbols = self._parse_sdk_library(lib_path)
# Check if this library's RAM is actually linked by seeing if any
# of its global BSS/DATA symbols appear in the ELF
if not global_ram_symbols & self._elf_symbol_names:
# No RAM from this library is in the ELF - skip it
continue
for name, size, section, is_local in ram_symbols:
# Skip if already in ELF or already seen from another lib
if name in self._elf_symbol_names or name in seen_symbols:
continue
# Only track symbols with non-zero size
if size > 0:
self._sdk_symbols.append(
SDKSymbol(
name=name,
size=size,
library=lib_name,
section=section,
is_local=is_local,
)
)
seen_symbols.add(name)
# Demangle SDK symbols for better readability
if self._sdk_symbols:
sdk_names = [sym.name for sym in self._sdk_symbols]
demangled_map = batch_demangle(sdk_names, objdump_path=self.objdump_path)
for sym in self._sdk_symbols:
sym.demangled = demangled_map.get(sym.name, sym.name)
# Sort by size descending for reporting
self._sdk_symbols.sort(key=lambda s: s.size, reverse=True)
total_sdk_ram = sum(s.size for s in self._sdk_symbols)
_LOGGER.debug(
"Found %d SDK symbols not in ELF, totaling %d bytes",
len(self._sdk_symbols),
total_sdk_ram,
)
def get_sdk_ram_symbols(self) -> list[SDKSymbol]:
"""Get SDK symbols that consume RAM but aren't in the ELF symbol table.
Returns:
List of SDKSymbol objects sorted by size descending.
"""
return self._sdk_symbols
def get_sdk_ram_by_library(self) -> dict[str, list[SDKSymbol]]:
"""Get SDK RAM symbols grouped by library.
Returns:
Dictionary mapping library name to list of symbols.
"""
by_lib: dict[str, list[SDKSymbol]] = defaultdict(list)
for sym in self._sdk_symbols:
by_lib[sym.library].append(sym)
return dict(by_lib)
if __name__ == "__main__":
from .cli import main

View File

@@ -1,16 +1,24 @@
"""CLI interface for memory analysis with report generation."""
from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable
import sys
from typing import TYPE_CHECKING
from . import (
_COMPONENT_API,
_COMPONENT_CORE,
_COMPONENT_PREFIX_ESPHOME,
_COMPONENT_PREFIX_EXTERNAL,
RAM_SECTIONS,
MemoryAnalyzer,
)
if TYPE_CHECKING:
from . import ComponentMemory
class MemoryAnalyzerCLI(MemoryAnalyzer):
"""Memory analyzer with CLI-specific report generation."""
@@ -19,6 +27,8 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
SYMBOL_SIZE_THRESHOLD: int = (
100 # Show symbols larger than this in detailed analysis
)
# Lower threshold for RAM symbols (RAM is more constrained)
RAM_SYMBOL_SIZE_THRESHOLD: int = 24
# Column width constants
COL_COMPONENT: int = 29
@@ -83,6 +93,60 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
COL_CORE_PERCENT,
)
def _add_section_header(self, lines: list[str], title: str) -> None:
"""Add a section header with title centered between separator lines."""
lines.append("")
lines.append("=" * self.TABLE_WIDTH)
lines.append(title.center(self.TABLE_WIDTH))
lines.append("=" * self.TABLE_WIDTH)
lines.append("")
def _add_top_consumers(
self,
lines: list[str],
title: str,
components: list[tuple[str, ComponentMemory]],
get_size: Callable[[ComponentMemory], int],
total: int,
memory_type: str,
limit: int = 25,
) -> None:
"""Add a formatted list of top memory consumers to the report.
Args:
lines: List of report lines to append the output to.
title: Section title to print before the list.
components: Sequence of (name, ComponentMemory) tuples to analyze.
get_size: Callable that takes a ComponentMemory and returns the
size in bytes to use for ranking and display.
total: Total size in bytes for computing percentage usage.
memory_type: Label for the memory region (e.g., "flash" or "RAM").
limit: Maximum number of components to include in the list.
"""
lines.append("")
lines.append(f"{title}:")
for i, (name, mem) in enumerate(components[:limit]):
size = get_size(mem)
if size > 0:
percentage = (size / total * 100) if total > 0 else 0
lines.append(
f"{i + 1}. {name} ({size:,} B) - {percentage:.1f}% of analyzed {memory_type}"
)
def _format_symbol_with_section(
self, demangled: str, size: int, section: str | None = None
) -> str:
"""Format a symbol entry, optionally adding a RAM section label.
If section is one of the RAM sections (.data or .bss), a label like
" [data]" or " [bss]" is appended. For non-RAM sections or when
section is None, no section label is added.
"""
section_label = ""
if section in RAM_SECTIONS:
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
return f"{demangled} ({size:,} B){section_label}"
def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report."""
components = sorted(
@@ -123,43 +187,70 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
f"{total_flash:>{self.COL_TOTAL_FLASH - 2},} B | {total_ram:>{self.COL_TOTAL_RAM - 2},} B"
)
# Top consumers
lines.append("")
lines.append("Top Flash Consumers:")
for i, (name, mem) in enumerate(components[:25]):
if mem.flash_total > 0:
percentage = (
(mem.flash_total / total_flash * 100) if total_flash > 0 else 0
)
lines.append(
f"{i + 1}. {name} ({mem.flash_total:,} B) - {percentage:.1f}% of analyzed flash"
)
lines.append("")
lines.append("Top RAM Consumers:")
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
for i, (name, mem) in enumerate(ram_components[:25]):
if mem.ram_total > 0:
percentage = (mem.ram_total / total_ram * 100) if total_ram > 0 else 0
lines.append(
f"{i + 1}. {name} ({mem.ram_total:,} B) - {percentage:.1f}% of analyzed RAM"
)
lines.append("")
lines.append(
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
# Show unattributed RAM (SDK/framework overhead)
unattributed_bss, unattributed_data, unattributed_total = (
self.get_unattributed_ram()
)
if unattributed_total > 0:
lines.append("")
lines.append(
f"Unattributed RAM: {unattributed_total:,} B (SDK/framework overhead)"
)
if unattributed_bss > 0 and unattributed_data > 0:
lines.append(
f" .bss: {unattributed_bss:,} B | .data: {unattributed_data:,} B"
)
# Show SDK symbol breakdown if available
sdk_by_lib = self.get_sdk_ram_by_library()
if sdk_by_lib:
lines.append("")
lines.append("SDK library breakdown (static symbols not in ELF):")
# Sort libraries by total size
lib_totals = [
(lib, sum(s.size for s in syms), syms)
for lib, syms in sdk_by_lib.items()
]
lib_totals.sort(key=lambda x: x[1], reverse=True)
for lib_name, lib_total, syms in lib_totals:
if lib_total == 0:
continue
lines.append(f" {lib_name}: {lib_total:,} B")
# Show top symbols from this library
for sym in sorted(syms, key=lambda s: s.size, reverse=True)[:3]:
section_label = sym.section.lstrip(".")
# Use demangled name (falls back to original if not demangled)
display_name = sym.demangled or sym.name
if len(display_name) > 50:
display_name = f"{display_name[:47]}..."
lines.append(
f" {sym.size:>6,} B [{section_label}] {display_name}"
)
# Top consumers
self._add_top_consumers(
lines,
"Top Flash Consumers",
components,
lambda m: m.flash_total,
total_flash,
"flash",
)
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
self._add_top_consumers(
lines,
"Top RAM Consumers",
ram_components,
lambda m: m.ram_total,
total_ram,
"RAM",
)
lines.append("=" * self.TABLE_WIDTH)
# Add ESPHome core detailed analysis if there are core symbols
if self._esphome_core_symbols:
lines.append("")
lines.append("=" * self.TABLE_WIDTH)
lines.append(
f"{_COMPONENT_CORE} Detailed Analysis".center(self.TABLE_WIDTH)
)
lines.append("=" * self.TABLE_WIDTH)
lines.append("")
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")
# Group core symbols by subcategory
core_subcategories: dict[str, list[tuple[str, str, int]]] = defaultdict(
@@ -211,7 +302,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
)
for i, (symbol, demangled, size) in enumerate(large_core_symbols):
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
# Core symbols only track (symbol, demangled, size) without section info,
# so we don't show section labels here
lines.append(
f"{i + 1}. {self._format_symbol_with_section(demangled, size)}"
)
lines.append("=" * self.TABLE_WIDTH)
@@ -267,11 +362,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
for comp_name, comp_mem in components_to_analyze:
if not (comp_symbols := self._component_symbols.get(comp_name, [])):
continue
lines.append("")
lines.append("=" * self.TABLE_WIDTH)
lines.append(f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH))
lines.append("=" * self.TABLE_WIDTH)
lines.append("")
self._add_section_header(lines, f"{comp_name} Detailed Analysis")
# Sort symbols by size
sorted_symbols = sorted(comp_symbols, key=lambda x: x[2], reverse=True)
@@ -282,19 +373,69 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
# Show all symbols above threshold for better visibility
large_symbols = [
(sym, dem, size)
for sym, dem, size in sorted_symbols
(sym, dem, size, sec)
for sym, dem, size, sec in sorted_symbols
if size > self.SYMBOL_SIZE_THRESHOLD
]
lines.append(
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
)
for i, (symbol, demangled, size) in enumerate(large_symbols):
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
lines.append(
f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}"
)
lines.append("=" * self.TABLE_WIDTH)
# Detailed RAM analysis by component (at end, before RAM strings analysis)
self._add_section_header(lines, "RAM Symbol Analysis by Component")
# Show top 15 RAM consumers with their large symbols
for name, mem in ram_components[:15]:
if mem.ram_total == 0:
continue
ram_syms = self._ram_symbols.get(name, [])
if not ram_syms:
continue
# Sort by size descending
sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True)
large_ram_syms = [
s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
]
lines.append(f"{name} ({mem.ram_total:,} B total RAM):")
# Show breakdown by section type
data_size = sum(s[2] for s in ram_syms if s[3] == ".data")
bss_size = sum(s[2] for s in ram_syms if s[3] == ".bss")
lines.append(f" .data (initialized): {data_size:,} B")
lines.append(f" .bss (uninitialized): {bss_size:,} B")
if large_ram_syms:
lines.append(
f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):"
)
for symbol, demangled, size, section in large_ram_syms[:10]:
# Format section label consistently by stripping leading dot
section_label = section.lstrip(".") if section else ""
# Add ellipsis if name is truncated
demangled_display = (
f"{demangled[:70]}..." if len(demangled) > 70 else demangled
)
lines.append(
f" {size:>6,} B [{section_label}] {demangled_display}"
)
if len(large_ram_syms) > 10:
lines.append(f" ... and {len(large_ram_syms) - 10} more")
lines.append("")
lines.append(
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
)
lines.append("=" * self.TABLE_WIDTH)
return "\n".join(lines)
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:

View File

@@ -7,11 +7,13 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
# Section mapping for ELF file sections
# Maps standard section names to their various platform-specific variants
# 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
SECTION_MAPPING = {
".text": frozenset([".text", ".iram"]),
".rodata": frozenset([".rodata"]),
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
".data": frozenset([".data", ".dram"]),
".bss": frozenset([".bss"]),
}
# Section to ComponentMemory attribute mapping
@@ -88,6 +90,77 @@ SYMBOL_PATTERNS = {
"sys_mbox_new",
"sys_arch_mbox_tryfetch",
],
# LibreTiny/Beken BK7231 radio calibration
"bk_radio_cal": [
"bk7011_",
"calibration_main",
"gcali_",
"rwnx_cal",
],
# LibreTiny/Beken WiFi MAC layer
"bk_wifi_mac": [
"rxu_", # RX upper layer
"txu_", # TX upper layer
"txl_", # TX lower layer
"rxl_", # RX lower layer
"scanu_", # Scan unit
"mm_hw_", # MAC management hardware
"mm_bcn", # MAC management beacon
"mm_tim", # MAC management TIM
"mm_check", # MAC management checks
"sm_connect", # Station management
"me_beacon", # Management entity beacon
"me_build", # Management entity build
"hapd_", # Host AP daemon
"chan_pre_", # Channel management
"handle_probe_", # Probe handling
],
# LibreTiny/Beken system control
"bk_system": [
"sctrl_", # System control
"icu_ctrl", # Interrupt control unit
"gdma_ctrl", # DMA control
"mpb_ctrl", # MPB control
"uf2_", # UF2 OTA
"bkreg_", # Beken registers
],
# LibreTiny/Beken BLE stack
"bk_ble": [
"gapc_", # GAP client
"gattc_", # GATT client
"attc_", # ATT client
"attmdb_", # ATT database
"atts_", # ATT server
"l2cc_", # L2CAP
"prf_env", # Profile environment
],
# LibreTiny/Beken scheduler
"bk_scheduler": [
"sch_plan_", # Scheduler plan
"sch_prog_", # Scheduler program
"sch_arb_", # Scheduler arbiter
],
# LibreTiny/Beken DMA descriptors
"bk_dma": [
"rx_payload_desc",
"rx_dma_hdrdesc",
"tx_hw_desc",
"host_event_data",
"host_cmd_data",
],
# ARM EABI compiler runtime (LibreTiny uses ARM Cortex-M)
"arm_runtime": [
"__aeabi_",
"__adddf3",
"__subdf3",
"__muldf3",
"__divdf3",
"__addsf3",
"__subsf3",
"__mulsf3",
"__divsf3",
"__gnu_unwind",
],
"xtensa": ["xt_", "_xt_", "xPortEnterCriticalTimeout"],
"heap": ["heap_", "multi_heap"],
"spi_flash": ["spi_flash"],
@@ -782,7 +855,22 @@ SYMBOL_PATTERNS = {
"math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"],
"character_class": ["__chclass"],
"camellia": ["camellia_", "camellia_feistel"],
"crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"],
"crypto_tables": [
"FSb",
"FSb2",
"FSb3",
"FSb4",
"Te0", # AES encryption table
"Td0", # AES decryption table
"crc32_table", # CRC32 lookup table
"crc_tab", # CRC lookup table
],
"crypto_hash": [
"SHA1Transform", # SHA1 hash function
"MD5Transform", # MD5 hash function
"SHA256",
"SHA512",
],
"event_buffer": ["g_eb_list_desc", "eb_space"],
"base_node": ["base_node_", "base_node_add_handler"],
"file_descriptor": ["s_fd_table"],

View File

@@ -5,6 +5,10 @@ from __future__ import annotations
import logging
from pathlib import Path
import subprocess
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Sequence
_LOGGER = logging.getLogger(__name__)
@@ -55,3 +59,35 @@ def find_tool(
_LOGGER.warning("Could not find %s tool", tool_name)
return None
def run_tool(
cmd: Sequence[str],
timeout: int = 30,
) -> subprocess.CompletedProcess[str] | None:
"""Run a toolchain command and return the result.
Args:
cmd: Command and arguments to run
timeout: Timeout in seconds
Returns:
CompletedProcess on success, None on failure
"""
try:
return subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired:
_LOGGER.warning("Command timed out: %s", " ".join(cmd))
return None
except FileNotFoundError:
_LOGGER.warning("Command not found: %s", cmd[0])
return None
except OSError as e:
_LOGGER.warning("Failed to run command %s: %s", cmd[0], e)
return None

View File

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

View File

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

View File

@@ -90,13 +90,16 @@ void AbsoluteHumidityComponent::loop() {
this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!"));
return;
}
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
// Calculate absolute humidity
const float absolute_humidity = vapor_density(es, hr, temperature_k);
ESP_LOGD(TAG,
"Saturation vapor pressure %f kPa\n"
"Publishing absolute humidity %f g/m³",
es, absolute_humidity);
// Publish absolute humidity
ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
this->status_clear_warning();
this->publish_state(absolute_humidity);
}

View File

@@ -211,13 +211,13 @@ void AcDimmer::write_state(float state) {
this->store_.value = new_value;
}
void AcDimmer::dump_config() {
ESP_LOGCONFIG(TAG, "AcDimmer:");
LOG_PIN(" Output Pin: ", this->gate_pin_);
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
ESP_LOGCONFIG(TAG,
"AcDimmer:\n"
" Min Power: %.1f%%\n"
" Init with half cycle: %s",
this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_));
LOG_PIN(" Output Pin: ", this->gate_pin_);
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
if (method_ == DIM_METHOD_LEADING_PULSE) {
ESP_LOGCONFIG(TAG, " Method: leading pulse");
} else if (method_ == DIM_METHOD_LEADING) {

View File

@@ -3,6 +3,7 @@ import esphome.codegen as cg
from esphome.components import output
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER
from esphome.core import CORE
CODEOWNERS = ["@glmnet"]
@@ -36,6 +37,12 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
if CORE.is_esp8266:
# ac_dimmer uses setTimer1Callback which requires the waveform generator
from esphome.components.esp8266.const import require_waveform
require_waveform()
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -121,23 +121,21 @@ void ADCSensor::setup() {
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
LOG_PIN(" Pin: ", this->pin_);
ESP_LOGCONFIG(TAG,
" Channel: %d\n"
" Unit: %s\n"
" Attenuation: %s\n"
" Samples: %i\n"
" Sampling mode: %s",
this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)),
this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_,
LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
ESP_LOGCONFIG(
TAG,
" Channel: %d\n"
" Unit: %s\n"
" Attenuation: %s\n"
" Samples: %i\n"
" Sampling mode: %s\n"
" Setup Status:\n"
" Handle Init: %s\n"
" Config: %s\n"
" Calibration: %s\n"
" Overall Init: %s",
this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)),
this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_,
LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)),
this->setup_flags_.handle_init_complete ? "OK" : "FAILED", this->setup_flags_.config_complete ? "OK" : "FAILED",
this->setup_flags_.calibration_complete ? "OK" : "FAILED", this->setup_flags_.init_complete ? "OK" : "FAILED");

View File

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

View File

@@ -162,11 +162,13 @@ void ADE7880::update() {
}
void ADE7880::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7880:");
ESP_LOGCONFIG(TAG,
"ADE7880:\n"
" Frequency: %.0f Hz",
this->frequency_);
LOG_PIN(" IRQ0 Pin: ", this->irq0_pin_);
LOG_PIN(" IRQ1 Pin: ", this->irq1_pin_);
LOG_PIN(" RESET Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_);
if (this->channel_a_ != nullptr) {
ESP_LOGCONFIG(TAG, " Phase A:");

View File

@@ -21,10 +21,12 @@ void ADS1115Sensor::update() {
void ADS1115Sensor::dump_config() {
LOG_SENSOR(" ", "ADS1115 Sensor", this);
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_);
ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_);
ESP_LOGCONFIG(TAG,
" Multiplexer: %u\n"
" Gain: %u\n"
" Resolution: %u\n"
" Sample rate: %u",
this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
}
} // namespace ads1115

View File

@@ -9,8 +9,10 @@ static const char *const TAG = "ads1118.sensor";
void ADS1118Sensor::dump_config() {
LOG_SENSOR(" ", "ADS1118 Sensor", this);
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
ESP_LOGCONFIG(TAG,
" Multiplexer: %u\n"
" Gain: %u",
this->multiplexer_, this->gain_);
}
float ADS1118Sensor::sample() {

View File

@@ -20,7 +20,8 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24);
ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str());
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str_to(addr_buf));
return true;
}
}

View File

@@ -1,4 +1,5 @@
#include "airthings_wave_base.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
// All information related to reading battery information came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
@@ -93,8 +94,10 @@ void AirthingsWaveBase::update() {
bool AirthingsWaveBase::request_read_values_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->sensors_data_characteristic_uuid_.to_str(char_buf));
return false;
}
@@ -117,17 +120,20 @@ bool AirthingsWaveBase::request_battery_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
if (chr == nullptr) {
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf));
return false;
}
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
if (descr == nullptr) {
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->access_control_point_characteristic_uuid_.to_str(char_buf));
return false;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,10 +15,8 @@ namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = espbt::ESPBTUUID::from_raw(
{0xa9, 0x7b, 0xb8, 0x85, 0x00, 0x1a, 0x28, 0xaa, 0x2a, 0x43, 0x6e, 0x03, 0xd1, 0xff, 0x9c, 0x85});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;

View File

@@ -67,8 +67,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
ESP_LOGW(TAG,
"[%s] No control service found at device, not an Anova..?\n"
"[%s] Note, this component does not currently support Anova Nano.",
this->get_name().c_str(), this->get_name().c_str());
break;
}
this->char_handle_ = chr->handle;

View File

@@ -226,32 +226,6 @@ def _encryption_schema(config):
return ENCRYPTION_SCHEMA(config)
def _validate_api_config(config: ConfigType) -> ConfigType:
"""Validate API configuration with mutual exclusivity check and deprecation warning."""
# Check if both password and encryption are configured
has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
has_encryption = CONF_ENCRYPTION in config
if has_password and has_encryption:
raise cv.Invalid(
"The 'password' and 'encryption' options are mutually exclusive. "
"The API client only supports one authentication method at a time. "
"Please remove one of them. "
"Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
"We strongly recommend using 'encryption' instead for better security."
)
# Warn about password deprecation
if has_password:
_LOGGER.warning(
"API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
"Please migrate to the 'encryption' configuration. "
"See https://esphome.io/components/api/#configuration-variables"
)
return config
def _consume_api_sockets(config: ConfigType) -> ConfigType:
"""Register socket needs for API component."""
from esphome.components import socket
@@ -268,7 +242,17 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
# Removed in 2026.1.0 - kept to provide helpful error message
cv.Optional(CONF_PASSWORD): cv.invalid(
"The 'password' option has been removed in ESPHome 2026.1.0.\n"
"Password authentication was deprecated in May 2022.\n"
"Please migrate to encryption for secure API communication:\n\n"
"api:\n"
" encryption:\n"
" key: !secret api_encryption_key\n\n"
"Generate a key with: openssl rand -base64 32\n"
"Or visit https://esphome.io/components/api/#configuration-variables"
),
cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
@@ -330,7 +314,6 @@ CONFIG_SCHEMA = cv.All(
}
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
_validate_api_config,
_consume_api_sockets,
)
@@ -344,9 +327,6 @@ async def to_code(config: ConfigType) -> None:
CORE.register_controller()
cg.add(var.set_port(config[CONF_PORT]))
if config[CONF_PASSWORD]:
cg.add_define("USE_API_PASSWORD")
cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
if CONF_LISTEN_BACKLOG in config:

View File

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

View File

@@ -13,6 +13,7 @@
#include <cinttypes>
#include <functional>
#include <limits>
#include <new>
#include <utility>
#ifdef USE_ESP8266
#include <pgmspace.h>
@@ -42,6 +43,9 @@
#ifdef USE_ZWAVE_PROXY
#include "esphome/components/zwave_proxy/zwave_proxy.h"
#endif
#ifdef USE_WATER_HEATER
#include "esphome/components/water_heater/water_heater.h"
#endif
namespace esphome::api {
@@ -93,21 +97,18 @@ static const int CAMERA_STOP_STREAM = 5000;
return;
#endif // USE_DEVICES
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto &noise_ctx = parent->get_noise_ctx();
if (noise_ctx.has_psk()) {
this->helper_ =
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
} else {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
}
#elif defined(USE_API_PLAINTEXT)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
this->helper_ = std::unique_ptr<APIFrameHelper>{
new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
#else
#error "No frame helper defined"
#endif
@@ -128,11 +129,13 @@ void APIConnection::start() {
this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
return;
}
this->client_info_.peername = helper_->getpeername();
this->client_info_.name = this->client_info_.peername;
// Initialize client name with peername (IP address) until Hello message provides actual name
const char *peername = this->helper_->get_client_peername();
this->helper_->set_client_name(peername, strlen(peername));
}
APIConnection::~APIConnection() {
this->destroy_active_iterator_();
#ifdef USE_BLUETOOTH_PROXY
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
@@ -145,6 +148,32 @@ APIConnection::~APIConnection() {
#endif
}
void APIConnection::destroy_active_iterator_() {
switch (this->active_iterator_) {
case ActiveIterator::LIST_ENTITIES:
this->iterator_storage_.list_entities.~ListEntitiesIterator();
break;
case ActiveIterator::INITIAL_STATE:
this->iterator_storage_.initial_state.~InitialStateIterator();
break;
case ActiveIterator::NONE:
break;
}
this->active_iterator_ = ActiveIterator::NONE;
}
void APIConnection::begin_iterator_(ActiveIterator type) {
this->destroy_active_iterator_();
this->active_iterator_ = type;
if (type == ActiveIterator::LIST_ENTITIES) {
new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
this->iterator_storage_.list_entities.begin();
} else {
new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
this->iterator_storage_.initial_state.begin();
}
}
void APIConnection::loop() {
if (this->flags_.next_close) {
// requested a disconnect
@@ -187,31 +216,42 @@ void APIConnection::loop() {
this->process_batch_();
}
if (!this->list_entities_iterator_.completed()) {
this->process_iterator_batch_(this->list_entities_iterator_);
} else if (!this->initial_state_iterator_.completed()) {
this->process_iterator_batch_(this->initial_state_iterator_);
// If we've completed initial states, process any remaining and clear the flag
if (this->initial_state_iterator_.completed()) {
// Process any remaining batched messages immediately
if (!this->deferred_batch_.empty()) {
this->process_batch_();
switch (this->active_iterator_) {
case ActiveIterator::LIST_ENTITIES:
if (this->iterator_storage_.list_entities.completed()) {
this->destroy_active_iterator_();
if (this->flags_.state_subscription) {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
} else {
this->process_iterator_batch_(this->iterator_storage_.list_entities);
}
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
// Release excess memory from buffers that grew during initial sync
this->deferred_batch_.release_buffer();
this->helper_->release_buffers();
}
break;
case ActiveIterator::INITIAL_STATE:
if (this->iterator_storage_.initial_state.completed()) {
this->destroy_active_iterator_();
// Process any remaining batched messages immediately
if (!this->deferred_batch_.empty()) {
this->process_batch_();
}
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
// Release excess memory from buffers that grew during initial sync
this->deferred_batch_.release_buffer();
this->helper_->release_buffers();
} else {
this->process_iterator_batch_(this->iterator_storage_.initial_state);
}
break;
case ActiveIterator::NONE:
break;
}
if (this->flags_.sent_ping) {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
on_fatal_error();
ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
this->client_info_.peername.c_str());
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
}
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
// Only send ping if we're not disconnecting
@@ -228,40 +268,24 @@ void APIConnection::loop() {
}
}
#ifdef USE_CAMERA
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_->available() == to_send;
CameraImageResponse msg;
msg.key = camera::Camera::instance()->get_object_id_hash();
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
msg.done = done;
#ifdef USE_DEVICES
msg.device_id = camera::Camera::instance()->get_device_id();
#endif
if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
this->image_reader_->consume_data(to_send);
if (done) {
this->image_reader_->return_image();
}
}
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
if (state_subs_at_ >= 0) {
this->process_state_subscriptions_();
}
#endif
#ifdef USE_CAMERA
// Process camera last - state updates are higher priority
// (missing a frame is fine, missing a state update is not)
this->try_send_camera_image_();
#endif
}
bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
this->flags_.next_close = true;
DisconnectResponse resp;
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
@@ -352,7 +376,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
bool is_single) {
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
ListEntitiesBinarySensorResponse msg;
msg.set_device_class(binary_sensor->get_device_class_ref());
msg.device_class = binary_sensor->get_device_class_ref();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
remaining_size, is_single);
@@ -384,7 +408,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt();
msg.supports_stop = traits.get_supports_stop();
msg.set_device_class(cover->get_device_class_ref());
msg.device_class = cover->get_device_class_ref();
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -419,7 +443,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
if (traits.supports_direction())
msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes() && fan->has_preset_mode())
msg.set_preset_mode(StringRef(fan->get_preset_mode()));
msg.preset_mode = StringRef(fan->get_preset_mode());
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -447,7 +471,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
if (msg.has_preset_mode)
call.set_preset_mode(msg.preset_mode);
call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
call.perform();
}
#endif
@@ -475,7 +499,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (light->supports_effects()) {
resp.set_effect(light->get_effect_name_ref());
resp.effect = light->get_effect_name_ref();
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -533,7 +557,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
if (msg.has_flash_length)
call.set_flash_length(msg.flash_length);
if (msg.has_effect)
call.set_effect(reinterpret_cast<const char *>(msg.effect), msg.effect_len);
call.set_effect(msg.effect.c_str(), msg.effect.size());
call.perform();
}
#endif
@@ -557,10 +581,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *sensor = static_cast<sensor::Sensor *>(entity);
ListEntitiesSensorResponse msg;
msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref());
msg.unit_of_measurement = sensor->get_unit_of_measurement_ref();
msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update();
msg.set_device_class(sensor->get_device_class_ref());
msg.device_class = sensor->get_device_class_ref();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -587,7 +611,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
auto *a_switch = static_cast<switch_::Switch *>(entity);
ListEntitiesSwitchResponse msg;
msg.assumed_state = a_switch->assumed_state();
msg.set_device_class(a_switch->get_device_class_ref());
msg.device_class = a_switch->get_device_class_ref();
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -612,7 +636,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
TextSensorStateResponse resp;
resp.set_state(StringRef(text_sensor->state));
resp.state = StringRef(text_sensor->state);
resp.missing_state = !text_sensor->has_state();
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -621,7 +645,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
ListEntitiesTextSensorResponse msg;
msg.set_device_class(text_sensor->get_device_class_ref());
msg.device_class = text_sensor->get_device_class_ref();
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
remaining_size, is_single);
}
@@ -651,13 +675,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode()));
resp.custom_fan_mode = StringRef(climate->get_custom_fan_mode());
}
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
}
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
resp.set_custom_preset(StringRef(climate->get_custom_preset()));
resp.custom_preset = StringRef(climate->get_custom_preset());
}
if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
@@ -712,11 +736,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
call.set_fan_mode(msg.custom_fan_mode);
call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
if (msg.has_preset)
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
if (msg.has_custom_preset)
call.set_preset(msg.custom_preset);
call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
if (msg.has_swing_mode)
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
call.perform();
@@ -742,9 +766,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *number = static_cast<number::Number *>(entity);
ListEntitiesNumberResponse msg;
msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref());
msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref();
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.set_device_class(number->traits.get_device_class_ref());
msg.device_class = number->traits.get_device_class_ref();
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step();
@@ -857,7 +881,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
bool is_single) {
auto *text = static_cast<text::Text *>(entity);
TextStateResponse resp;
resp.set_state(StringRef(text->state));
resp.state = StringRef(text->state);
resp.missing_state = !text->has_state();
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -869,7 +893,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length();
msg.set_pattern(text->traits.get_pattern_ref());
msg.pattern = text->traits.get_pattern_ref();
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -890,7 +914,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
SelectStateResponse resp;
resp.set_state(StringRef(select->current_option()));
resp.state = StringRef(select->current_option());
resp.missing_state = !select->has_state();
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -905,7 +929,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
}
void APIConnection::select_command(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
call.set_option(reinterpret_cast<const char *>(msg.state), msg.state_len);
call.set_option(msg.state.c_str(), msg.state.size());
call.perform();
}
#endif
@@ -915,7 +939,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *button = static_cast<button::Button *>(entity);
ListEntitiesButtonResponse msg;
msg.set_device_class(button->get_device_class_ref());
msg.device_class = button->get_device_class_ref();
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -984,7 +1008,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
auto *valve = static_cast<valve::Valve *>(entity);
ListEntitiesValveResponse msg;
auto traits = valve->get_traits();
msg.set_device_class(valve->get_device_class_ref());
msg.device_class = valve->get_device_class_ref();
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop();
@@ -1029,7 +1053,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
for (auto &supported_format : traits.get_supported_formats()) {
msg.supported_formats.emplace_back();
auto &media_format = msg.supported_formats.back();
media_format.set_format(StringRef(supported_format.format));
media_format.format = StringRef(supported_format.format);
media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
@@ -1057,6 +1081,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
#endif
#ifdef USE_CAMERA
void APIConnection::try_send_camera_image_() {
if (!this->image_reader_)
return;
// Send as many chunks as possible without blocking
while (this->image_reader_->available()) {
if (!this->helper_->can_write_without_blocking())
return;
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_->available() == to_send;
CameraImageResponse msg;
msg.key = camera::Camera::instance()->get_object_id_hash();
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
msg.done = done;
#ifdef USE_DEVICES
msg.device_id = camera::Camera::instance()->get_device_id();
#endif
if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
return; // Send failed, try again later
}
this->image_reader_->consume_data(to_send);
if (done) {
this->image_reader_->return_image();
return;
}
}
}
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
if (!this->flags_.state_subscription)
return;
@@ -1064,8 +1118,11 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
return;
if (this->image_reader_->available())
return;
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
this->image_reader_->set_image(std::move(image));
// Try to send immediately to reduce latency
this->try_send_camera_image_();
}
}
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -1094,9 +1151,8 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr) {
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE
if (value.timezone_len > 0) {
homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast<const char *>(value.timezone),
value.timezone_len);
if (!value.timezone.empty()) {
homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size());
}
#endif
}
@@ -1207,8 +1263,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
for (auto &wake_word : config.available_wake_words) {
resp.available_wake_words.emplace_back();
auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.set_id(StringRef(wake_word.id));
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
resp_wake_word.id = StringRef(wake_word.id);
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
@@ -1223,8 +1279,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
resp.available_wake_words.emplace_back();
auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.set_id(StringRef(wake_word.id));
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
resp_wake_word.id = StringRef(wake_word.id);
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
@@ -1306,6 +1362,57 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
}
#endif
#ifdef USE_WATER_HEATER
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state,
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
WaterHeaterStateResponse resp;
resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
resp.current_temperature = wh->get_current_temperature();
resp.target_temperature = wh->get_target_temperature();
resp.target_temperature_low = wh->get_target_temperature_low();
resp.target_temperature_high = wh->get_target_temperature_high();
resp.state = wh->get_state();
resp.key = wh->get_object_id_hash();
return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
ListEntitiesWaterHeaterResponse msg;
auto traits = wh->get_traits();
msg.min_temperature = traits.get_min_temperature();
msg.max_temperature = traits.get_max_temperature();
msg.target_temperature_step = traits.get_target_temperature_step();
msg.supported_modes = &traits.get_supported_modes();
msg.supported_features = traits.get_feature_flags();
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE)
call.set_target_temperature(msg.target_temperature);
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW)
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) {
call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
}
call.perform();
}
#endif
#ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const char *event_type) {
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
@@ -1314,7 +1421,7 @@ void APIConnection::send_event(event::Event *event, const char *event_type) {
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
EventResponse resp;
resp.set_event_type(StringRef(event_type));
resp.event_type = StringRef(event_type);
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1322,7 +1429,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
bool is_single) {
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.set_device_class(event->get_device_class_ref());
msg.device_class = event->get_device_class_ref();
msg.event_types = &event->get_event_types();
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -1345,11 +1452,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
resp.has_progress = true;
resp.progress = update->update_info.progress;
}
resp.set_current_version(StringRef(update->update_info.current_version));
resp.set_latest_version(StringRef(update->update_info.latest_version));
resp.set_title(StringRef(update->update_info.title));
resp.set_release_summary(StringRef(update->update_info.summary));
resp.set_release_url(StringRef(update->update_info.release_url));
resp.current_version = StringRef(update->update_info.current_version);
resp.latest_version = StringRef(update->update_info.latest_version);
resp.title = StringRef(update->update_info.title);
resp.release_summary = StringRef(update->update_info.summary);
resp.release_url = StringRef(update->update_info.release_url);
}
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1357,7 +1464,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *update = static_cast<update::UpdateEntity *>(entity);
ListEntitiesUpdateResponse msg;
msg.set_device_class(update->get_device_class_ref());
msg.device_class = update->get_device_class_ref();
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1395,9 +1502,10 @@ void APIConnection::complete_authentication_() {
}
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
std::string(this->helper_->get_client_peername()));
#endif
#ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) {
@@ -1412,41 +1520,25 @@ void APIConnection::complete_authentication_() {
}
bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_info_.peername = this->helper_->getpeername();
// Copy client name with truncation if needed (set_client_name handles truncation)
this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size());
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(),
this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_);
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 13;
resp.api_version_minor = 14;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_name(StringRef(App.get_name()));
resp.server_info = ESPHOME_VERSION_REF;
resp.name = StringRef(App.get_name());
#ifdef USE_API_PASSWORD
// Password required - wait for authentication
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
#else
// No password configured - auto-authenticate
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
this->complete_authentication_();
#endif
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
}
#ifdef USE_API_PASSWORD
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
AuthenticationResponse resp;
// bool invalid_password = 1;
resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
if (!resp.invalid_password) {
this->complete_authentication_();
}
return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
}
#endif // USE_API_PASSWORD
bool APIConnection::send_ping_response(const PingRequest &msg) {
PingResponse resp;
@@ -1455,24 +1547,24 @@ bool APIConnection::send_ping_response(const PingRequest &msg) {
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
DeviceInfoResponse resp{};
#ifdef USE_API_PASSWORD
resp.uses_password = true;
#endif
resp.set_name(StringRef(App.get_name()));
resp.set_friendly_name(StringRef(App.get_friendly_name()));
resp.name = StringRef(App.get_name());
resp.friendly_name = StringRef(App.get_friendly_name());
#ifdef USE_AREAS
resp.set_suggested_area(StringRef(App.get_area()));
resp.suggested_area = StringRef(App.get_area());
#endif
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char mac_address[18];
uint8_t mac[6];
get_mac_address_raw(mac);
format_mac_addr_upper(mac, mac_address);
resp.set_mac_address(StringRef(mac_address));
resp.mac_address = StringRef(mac_address);
resp.set_esphome_version(ESPHOME_VERSION_REF);
resp.esphome_version = ESPHOME_VERSION_REF;
resp.set_compilation_time(App.get_compilation_time_ref());
// Stack buffer for build time string
char build_time_str[Application::BUILD_TIME_STR_SIZE];
App.get_build_time_string(build_time_str);
resp.compilation_time = StringRef(build_time_str);
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
#if defined(USE_ESP8266) || defined(USE_ESP32)
@@ -1496,10 +1588,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER;
char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)];
memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM));
resp.set_manufacturer(StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1));
resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1);
#else
static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER);
resp.set_manufacturer(MANUFACTURER);
resp.manufacturer = MANUFACTURER;
#endif
#undef ESPHOME_MANUFACTURER
@@ -1507,10 +1599,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD;
char model_buf[sizeof(MODEL_PROGMEM)];
memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM));
resp.set_model(StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1));
resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1);
#else
static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
resp.set_model(MODEL);
resp.model = MODEL;
#endif
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
@@ -1523,13 +1615,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)];
memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM));
memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM));
resp.set_project_name(StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1));
resp.set_project_version(StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1));
resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1);
resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1);
#else
static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
resp.set_project_name(PROJECT_NAME);
resp.set_project_version(PROJECT_VERSION);
resp.project_name = PROJECT_NAME;
resp.project_version = PROJECT_VERSION;
#endif
#endif
#ifdef USE_WEBSERVER
@@ -1540,7 +1632,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char bluetooth_mac[18];
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
resp.bluetooth_mac_address = StringRef(bluetooth_mac);
#endif
#ifdef USE_VOICE_ASSISTANT
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
@@ -1559,7 +1651,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
break;
auto &device_info = resp.devices[device_index++];
device_info.device_id = device->get_device_id();
device_info.set_name(StringRef(device->get_name()));
device_info.name = StringRef(device->get_name());
device_info.area_id = device->get_area_id();
}
#endif
@@ -1570,7 +1662,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
break;
auto &area_info = resp.areas[area_index++];
area_info.area_id = area->get_area_id();
area_info.set_name(StringRef(area->get_name()));
area_info.name = StringRef(area->get_name());
}
#endif
@@ -1579,15 +1671,38 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs()) {
// Compare entity_id and attribute with message fields
bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0);
bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) ||
(it.attribute == nullptr && msg.attribute.empty());
// Skip if entity_id is empty (invalid message)
if (msg.entity_id.empty()) {
return;
}
if (entity_match && attribute_match) {
it.callback(msg.state);
for (auto &it : this->parent_->get_state_subs()) {
// Compare entity_id: check length matches and content matches
size_t entity_id_len = strlen(it.entity_id);
if (entity_id_len != msg.entity_id.size() ||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
continue;
}
// Compare attribute: either both have matching attribute, or both have none
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
if (sub_attr_len != msg.attribute.size() ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
continue;
}
// Create null-terminated state for callback (parse_number needs null-termination)
// HA state max length is 255, so 256 byte buffer covers all cases
char state_buf[256];
size_t copy_len = msg.state.size();
if (copy_len >= sizeof(state_buf)) {
copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator
}
if (copy_len > 0) {
memcpy(state_buf, msg.state.c_str(), copy_len);
}
state_buf[copy_len] = '\0';
it.callback(StringRef(state_buf, copy_len));
}
}
#endif
@@ -1622,20 +1737,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
// the action list. This ensures async actions (delays, waits) complete first.
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) {
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.error_message = error_message;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.error_message = error_message;
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
@@ -1663,13 +1778,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
resp.success = false;
psk_t psk{};
if (msg.key.empty()) {
if (msg.key_len == 0) {
if (this->parent_->clear_noise_psk(true)) {
resp.success = true;
} else {
ESP_LOGW(TAG, "Failed to clear encryption key");
}
} else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) {
} else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length");
} else if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key");
@@ -1704,10 +1819,30 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
return false;
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
if (!this->try_to_clear_buffer(!is_log_message)) {
return false;
}
// Toggle Nagle's algorithm based on message type to prevent log messages from
// filling the TCP send buffer and crowding out important state updates.
//
// This honors the `no_delay` proto option - SubscribeLogsResponse is the only
// message with `option (no_delay) = false;` in api.proto, indicating it should
// allow Nagle coalescing. This option existed since 2019 but was never implemented.
//
// - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce
// into fewer, larger packets. They flush naturally via TCP delayed ACK timer
// (~200ms), buffer filling, or when a state update triggers a flush.
//
// - All other messages (state updates, responses): Disable Nagle (NODELAY=true)
// for immediate delivery. These are time-sensitive and should not be delayed.
//
// This must be done proactively BEFORE the buffer fills up - checking buffer
// state here would be too late since we'd already be in a degraded state.
this->helper_->set_nodelay(!is_log_message);
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
if (err == APIError::WOULD_BLOCK)
return false;
@@ -1718,15 +1853,9 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
// Do not set last_traffic_ on send
return true;
}
#ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
}
#endif
void APIConnection::on_no_setup_connection() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
}
void APIConnection::on_fatal_error() {
this->helper_->close();
@@ -1772,9 +1901,9 @@ bool APIConnection::schedule_batch_() {
}
void APIConnection::process_batch_() {
// Ensure PacketInfo remains trivially destructible for our placement new approach
static_assert(std::is_trivially_destructible<PacketInfo>::value,
"PacketInfo must remain trivially destructible with this placement-new approach");
// Ensure MessageInfo remains trivially destructible for our placement new approach
static_assert(std::is_trivially_destructible<MessageInfo>::value,
"MessageInfo must remain trivially destructible with this placement-new approach");
if (this->deferred_batch_.empty()) {
this->flags_.batch_scheduled = false;
@@ -1814,12 +1943,12 @@ void APIConnection::process_batch_() {
return;
}
size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH);
size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH);
// Stack-allocated array for packet info
alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)];
PacketInfo *packet_info = reinterpret_cast<PacketInfo *>(packet_info_storage);
size_t packet_count = 0;
// Stack-allocated array for message info
alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
MessageInfo *message_info = reinterpret_cast<MessageInfo *>(message_info_storage);
size_t message_count = 0;
// Cache these values to avoid repeated virtual calls
const uint8_t header_padding = this->helper_->frame_header_padding();
@@ -1850,7 +1979,7 @@ void APIConnection::process_batch_() {
uint32_t current_offset = 0;
// Process items and encode directly to buffer (up to our limit)
for (size_t i = 0; i < packets_to_process; i++) {
for (size_t i = 0; i < messages_to_process; i++) {
const auto &item = this->deferred_batch_[i];
// Try to encode message
// The creator will calculate overhead to determine if the message fits
@@ -1864,11 +1993,11 @@ void APIConnection::process_batch_() {
// Message was encoded successfully
// payload_size is header_padding + actual payload size + footer_size
uint16_t proto_payload_size = payload_size - header_padding - footer_size;
// Use placement new to construct PacketInfo in pre-allocated stack array
// This avoids default-constructing all MAX_PACKETS_PER_BATCH elements
// Explicit destruction is not needed because PacketInfo is trivially destructible,
// Use placement new to construct MessageInfo in pre-allocated stack array
// This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
// Explicit destruction is not needed because MessageInfo is trivially destructible,
// as ensured by the static_assert in its definition.
new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size);
new (&message_info[message_count++]) MessageInfo(item.message_type, current_offset, proto_payload_size);
// Update tracking variables
items_processed++;
@@ -1892,9 +2021,9 @@ void APIConnection::process_batch_() {
shared_buf.resize(shared_buf.size() + footer_size);
}
// Send all collected packets
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
std::span<const PacketInfo>(packet_info, packet_count));
// Send all collected messages
APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
std::span<const MessageInfo>(message_info, message_count));
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
}
@@ -1962,10 +2091,10 @@ void APIConnection::process_state_subscriptions_() {
const auto &it = subs[this->state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.set_entity_id(StringRef(it.entity_id));
resp.entity_id = StringRef(it.entity_id);
// Avoid string copy by using the const char* pointer if it exists
resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""));
resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef("");
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
@@ -1974,8 +2103,13 @@ void APIConnection::process_state_subscriptions_() {
}
#endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_client_(int level, const LogString *message) {
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(),
this->helper_->get_client_peername(), LOG_STR_ARG(message));
}
void APIConnection::log_warning_(const LogString *message, APIError err) {
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(),
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
}

View File

@@ -9,32 +9,23 @@
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/string_ref.h"
#include <functional>
#include <vector>
namespace esphome::api {
// Client information structure
struct ClientInfo {
std::string name; // Client name from Hello message
std::string peername; // IP:port from socket
};
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending
// This was increased from 20 to 24 after removing the unique_id field from entity info messages,
// which reduced message sizes allowing more entities per batch without exceeding packet limits
static constexpr size_t MAX_INITIAL_PER_BATCH = 24;
// Maximum number of packets to process in a single batch (platform-dependent)
// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_
// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes
#if defined(USE_ESP32) || defined(USE_HOST)
static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HOST has plenty
#else
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
#endif
// API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch
// TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then
static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id)
static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id)
// Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
class APIConnection final : public APIServerConnection {
public:
@@ -176,6 +167,11 @@ class APIConnection final : public APIServerConnection {
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif
#ifdef USE_WATER_HEATER
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_EVENT
void send_event(event::Event *event, const char *event_type);
#endif
@@ -197,16 +193,17 @@ class APIConnection final : public APIServerConnection {
void on_get_time_response(const GetTimeResponse &value) override;
#endif
bool send_hello_response(const HelloRequest &msg) override;
#ifdef USE_API_PASSWORD
bool send_authenticate_response(const AuthenticationRequest &msg) override;
#endif
bool send_disconnect_response(const DisconnectRequest &msg) override;
bool send_ping_response(const PingRequest &msg) override;
bool send_device_info_response(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void subscribe_states(const SubscribeStatesRequest &msg) override {
this->flags_.state_subscription = true;
this->initial_state_iterator_.begin();
// Start initial state iterator only if no iterator is active
// If list_entities is running, we'll start initial_state when it completes
if (this->active_iterator_ == ActiveIterator::NONE) {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
}
void subscribe_logs(const SubscribeLogsRequest &msg) override {
this->flags_.log_subscription = msg.level;
@@ -224,9 +221,9 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_USER_DEFINED_ACTIONS
void execute_service(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message);
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -251,9 +248,6 @@ class APIConnection final : public APIServerConnection {
}
void on_fatal_error() override;
#ifdef USE_API_PASSWORD
void on_unauthenticated_access() override;
#endif
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen
@@ -280,13 +274,18 @@ class APIConnection final : public APIServerConnection {
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
const std::string &get_name() const { return this->client_info_.name; }
const std::string &get_peername() const { return this->client_info_.peername; }
const char *get_name() const { return this->helper_->get_client_name(); }
/// Get peer name (IP address) - cached at connection init time
const char *get_peername() const { return this->helper_->get_client_peername(); }
protected:
// Helper function to handle authentication completion
void complete_authentication_();
#ifdef USE_CAMERA
void try_send_camera_image_();
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void process_state_subscriptions_();
#endif
@@ -310,25 +309,24 @@ class APIConnection final : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
// Try to use static reference first to avoid allocation
StringRef static_ref = entity->get_object_id_ref_for_api_();
// Store dynamic string outside the if-else to maintain lifetime
std::string object_id;
if (!static_ref.empty()) {
msg.set_object_id(static_ref);
} else {
// Dynamic case - need to allocate
object_id = entity->get_object_id();
msg.set_object_id(StringRef(object_id));
// API 1.14+ clients compute object_id client-side from the entity name
// For older clients, we must send object_id for backward compatibility
// See: https://github.com/esphome/backlog/issues/76
// TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
// Buffer must remain in scope until encode_message_to_buffer is called
char object_id_buf[OBJECT_ID_MAX_LEN];
if (!conn->client_supports_api_version(1, 14)) {
msg.object_id = entity->get_object_id_to(object_id_buf);
}
if (entity->has_own_name()) {
msg.set_name(entity->get_name());
msg.name = entity->get_name();
}
// Set common EntityBase properties
#ifdef USE_ENTITY_ICON
msg.set_icon(entity->get_icon_ref());
msg.icon = entity->get_icon_ref();
#endif
msg.disabled_by_default = entity->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
@@ -343,16 +341,24 @@ class APIConnection final : public APIServerConnection {
inline bool check_voice_assistant_api_connection_() const;
#endif
// Get the max batch size based on client API version
// API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch
// TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly
size_t get_max_batch_size_() const {
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
}
// Helper method to process multiple entities from an iterator in a batch
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
size_t initial_size = this->deferred_batch_.size();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) {
size_t max_batch = this->get_max_batch_size_();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
iterator.advance();
}
// If the batch is full, process it immediately
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) {
if (this->deferred_batch_.size() >= max_batch) {
this->process_batch_();
}
}
@@ -456,6 +462,12 @@ class APIConnection final : public APIServerConnection {
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_WATER_HEATER
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
@@ -490,18 +502,27 @@ class APIConnection final : public APIServerConnection {
std::unique_ptr<APIFrameHelper> helper_;
APIServer *parent_;
// Group 2: Larger objects (must be 4-byte aligned)
// These contain vectors/pointers internally, so putting them early ensures good alignment
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
// Group 2: Iterator union (saves ~16 bytes vs separate iterators)
// These iterators are never active simultaneously - list_entities runs to completion
// before initial_state begins, so we use a union with explicit construction/destruction.
enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE };
union IteratorUnion {
ListEntitiesIterator list_entities;
InitialStateIterator initial_state;
// Constructor/destructor do nothing - use placement new/explicit destructor
IteratorUnion() {}
~IteratorUnion() {}
} iterator_storage_;
// Helper methods for iterator lifecycle management
void destroy_active_iterator_();
void begin_iterator_(ActiveIterator type);
#ifdef USE_CAMERA
std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif
// Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each)
ClientInfo client_info_;
// Group 4: 4-byte types
// Group 3: 4-byte types
uint32_t last_traffic_;
#ifdef USE_API_HOMEASSISTANT_STATES
int state_subs_at_ = -1;
@@ -608,7 +629,9 @@ class APIConnection final : public APIServerConnection {
// 2-byte types immediately after flags_ (no padding between them)
uint16_t client_api_version_major_{0};
uint16_t client_api_version_minor_{0};
// Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary
// 1-byte type to fill padding
ActiveIterator active_iterator_{ActiveIterator::NONE};
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
uint32_t get_batch_delay_ms_() const;
// Message will use 8 more bytes than the minimum size, and typical
@@ -726,6 +749,8 @@ class APIConnection final : public APIServerConnection {
return this->schedule_batch_();
}
// Helper function to log client messages with name and peername
void log_client_(int level, const LogString *message);
// Helper function to log API errors with errno
void log_warning_(const LogString *message, APIError err);
// Helper to handle fatal errors with logging

View File

@@ -1,6 +1,5 @@
#include "api_frame_helper.h"
#ifdef USE_API
#include "api_connection.h" // For ClientInfo struct
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -13,12 +12,29 @@ namespace esphome::api {
static const char *const TAG = "api.frame_helper";
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
#define LOG_PACKET_RECEIVED(buffer) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)
@@ -229,6 +245,8 @@ APIError APIFrameHelper::init_common_() {
HELPER_LOG("Bad state for init %d", (int) state_);
return APIError::BAD_STATE;
}
// Cache peername now while socket is valid - needed for error logging after socket failure
this->socket_->getpeername_to(this->client_peername_);
int err = this->socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;

View File

@@ -29,24 +29,28 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266
static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms
#endif
// Forward declaration
struct ClientInfo;
// Maximum number of messages to batch in a single write operation
// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there)
static constexpr size_t MAX_MESSAGES_PER_BATCH = 34;
class ProtoWriteBuffer;
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
struct ReadPacketBuffer {
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
uint16_t data_len;
uint16_t type;
};
// Packed packet info structure to minimize memory usage
struct PacketInfo {
// Packed message info structure to minimize memory usage
struct MessageInfo {
uint16_t offset; // Offset in buffer where message starts
uint16_t payload_size; // Size of the message payload
uint8_t message_type; // Message type (0-255)
PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
MessageInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
};
enum class APIError : uint16_t {
@@ -82,14 +86,23 @@ const LogString *api_error_to_logstr(APIError err);
class APIFrameHelper {
public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: socket_(std::move(socket)), client_info_(client_info) {}
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
// Get client name (null-terminated)
const char *get_client_name() const { return this->client_name_; }
// Get client peername/IP (null-terminated, cached at init time for availability after socket failure)
const char *get_client_peername() const { return this->client_peername_; }
// Set client name from buffer with length (truncates if needed)
void set_client_name(const char *name, size_t len) {
size_t copy_len = std::min(len, sizeof(this->client_name_) - 1);
memcpy(this->client_name_, name, copy_len);
this->client_name_[copy_len] = '\0';
}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
APIError close() {
state_ = State::CLOSED;
@@ -107,11 +120,32 @@ class APIFrameHelper {
}
return APIError::OK;
}
/// Toggle TCP_NODELAY socket option to control Nagle's algorithm.
///
/// This is used to allow log messages to coalesce (Nagle enabled) while keeping
/// state updates low-latency (NODELAY enabled). Without this, many small log
/// packets fill the TCP send buffer, crowding out important state updates.
///
/// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040)
/// this is just a boolean assignment; on other platforms it's a lightweight syscall.
///
/// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle
/// @return true if successful or already in desired state
bool set_nodelay(bool enable) {
if (this->nodelay_enabled_ == enable)
return true;
int val = enable ? 1 : 0;
int err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
if (err == 0) {
this->nodelay_enabled_ = enable;
}
return err == 0;
}
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
// Write multiple protobuf packets in a single operation
// packets contains (message_type, offset, length) for each message in the buffer
// Write multiple protobuf messages in a single operation
// messages contains (message_type, offset, length) for each message in the buffer
// The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
virtual APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) = 0;
// Get the frame header padding required by this protocol
uint8_t frame_header_padding() const { return frame_header_padding_; }
// Get the frame footer size required by this protocol
@@ -127,12 +161,6 @@ class APIFrameHelper {
// Use swap trick since shrink_to_fit() is non-binding and may be ignored
std::vector<uint8_t>().swap(this->rx_buf_);
}
// reusable_iovs_: Safe to release unconditionally.
// Only used within write_protobuf_packets() calls - cleared at start,
// populated with pointers, used for writev(), then function returns.
// The iovecs contain stale pointers after the call (data was either sent
// or copied to tx_buf_), and are cleared on next write_protobuf_packets().
std::vector<struct iovec>().swap(this->reusable_iovs_);
}
protected:
@@ -186,12 +214,12 @@ class APIFrameHelper {
// Containers (size varies, but typically 12+ bytes on 32-bit)
std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
std::vector<struct iovec> reusable_iovs_;
std::vector<uint8_t> rx_buf_;
// Pointer to client info (4 bytes on 32-bit)
// Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
const ClientInfo *client_info_{nullptr};
// Client name buffer - stores name from Hello message or initial peername
char client_name_[CLIENT_INFO_NAME_MAX_LEN]{};
// Cached peername/IP address - captured at init time for availability after socket failure
char client_peername_[socket::SOCKADDR_STR_LEN]{};
// Group smaller types together
uint16_t rx_buf_len_ = 0;
@@ -201,7 +229,10 @@ class APIFrameHelper {
uint8_t tx_buf_head_{0};
uint8_t tx_buf_tail_{0};
uint8_t tx_buf_count_{0};
// 8 bytes total, 0 bytes padding
// Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true
// since init_common_() enables NODELAY. Used by set_nodelay() to allow log
// messages to coalesce while keeping state updates low-latency.
bool nodelay_enabled_{true};
// Common initialization for both plaintext and noise protocols
APIError init_common_();

View File

@@ -24,12 +24,29 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
#define LOG_PACKET_RECEIVED(buffer) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)
@@ -415,12 +432,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
// Resize to include MAC space (required for Noise encryption)
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
PacketInfo packet{type, 0,
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
MessageInfo msg{type, 0,
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
}
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
APIError aerr = state_action_();
if (aerr != APIError::OK) {
return aerr;
@@ -430,20 +447,20 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
return APIError::WOULD_BLOCK;
}
if (packets.empty()) {
if (messages.empty()) {
return APIError::OK;
}
uint8_t *buffer_data = buffer.get_buffer()->data();
this->reusable_iovs_.clear();
this->reusable_iovs_.reserve(packets.size());
// Stack-allocated iovec array - no heap allocation
StaticVector<struct iovec, MAX_MESSAGES_PER_BATCH> iovs;
uint16_t total_write_len = 0;
// We need to encrypt each packet in place
for (const auto &packet : packets) {
// We need to encrypt each message in place
for (const auto &msg : messages) {
// The buffer already has padding at offset
uint8_t *buf_start = buffer_data + packet.offset;
uint8_t *buf_start = buffer_data + msg.offset;
// Write noise header
buf_start[0] = 0x01; // indicator
@@ -451,10 +468,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
// Write message header (to be encrypted)
const uint8_t msg_offset = 3;
buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
buf_start[msg_offset] = static_cast<uint8_t>(msg.message_type >> 8); // type high byte
buf_start[msg_offset + 1] = static_cast<uint8_t>(msg.message_type); // type low byte
buf_start[msg_offset + 2] = static_cast<uint8_t>(msg.payload_size >> 8); // data_len high byte
buf_start[msg_offset + 3] = static_cast<uint8_t>(msg.payload_size); // data_len low byte
// payload data is already in the buffer starting at offset + 7
// Make sure we have space for MAC
@@ -463,8 +480,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
// Encrypt the message in place
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
4 + packet.payload_size + frame_footer_size_);
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + msg.payload_size,
4 + msg.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
APIError aerr =
@@ -476,14 +493,14 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
buf_start[2] = static_cast<uint8_t>(mbuf.size);
// Add iovec for this encrypted packet
size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
this->reusable_iovs_.push_back({buf_start, packet_len});
total_write_len += packet_len;
// Add iovec for this encrypted message
size_t msg_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
iovs.push_back({buf_start, msg_len});
total_write_len += msg_len;
}
// Send all encrypted packets in one writev call
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
// Send all encrypted messages in one writev call
return this->write_raw_(iovs.data(), iovs.size(), total_write_len);
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
@@ -539,7 +556,8 @@ APIError APINoiseFrameHelper::init_handshake_() {
if (aerr != APIError::OK)
return aerr;
// set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {};
// Use swap idiom to actually release memory (= {} only clears size, not capacity)
std::vector<uint8_t>().swap(prologue_);
err = noise_handshakestate_start(handshake_);
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);

View File

@@ -9,8 +9,8 @@ namespace esphome::api {
class APINoiseFrameHelper final : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(ctx) {
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx)
: APIFrameHelper(std::move(socket)), ctx_(ctx) {
// Noise header structure:
// Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian)
@@ -23,7 +23,7 @@ class APINoiseFrameHelper final : public APIFrameHelper {
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
protected:
APIError state_action_();

View File

@@ -1,7 +1,6 @@
#include "api_frame_helper_plaintext.h"
#ifdef USE_API
#ifdef USE_API_PLAINTEXT
#include "api_connection.h" // For ClientInfo struct
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -18,12 +17,29 @@ namespace esphome::api {
static const char *const TAG = "api.plaintext";
#define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t API_MAX_LOG_BYTES = 168;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
#else
#define HELPER_LOG(msg, ...) ((void) 0)
#endif
#ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
#define LOG_PACKET_RECEIVED(buffer) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Received frame: %s", \
format_hex_pretty_to(hex_buf_, (buffer).data(), \
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
} while (0)
#define LOG_PACKET_SENDING(data, len) \
do { \
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
ESP_LOGVV(TAG, "Sending raw: %s", \
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
} while (0)
#else
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
#define LOG_PACKET_SENDING(data, len) ((void) 0)
@@ -216,29 +232,30 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::OK;
}
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
}
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer,
std::span<const MessageInfo> messages) {
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
if (packets.empty()) {
if (messages.empty()) {
return APIError::OK;
}
uint8_t *buffer_data = buffer.get_buffer()->data();
this->reusable_iovs_.clear();
this->reusable_iovs_.reserve(packets.size());
// Stack-allocated iovec array - no heap allocation
StaticVector<struct iovec, MAX_MESSAGES_PER_BATCH> iovs;
uint16_t total_write_len = 0;
for (const auto &packet : packets) {
for (const auto &msg : messages) {
// Calculate varint sizes for header layout
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.payload_size));
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.message_type));
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
// Calculate where to start writing the header
@@ -266,25 +283,25 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
//
// The message starts at offset + frame_header_padding_
// So we write the header starting at offset + frame_header_padding_ - total_header_len
uint8_t *buf_start = buffer_data + packet.offset;
uint8_t *buf_start = buffer_data + msg.offset;
uint32_t header_offset = frame_header_padding_ - total_header_len;
// Write the plaintext header
buf_start[header_offset] = 0x00; // indicator
// Encode varints directly into buffer
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
ProtoVarInt(packet.message_type)
ProtoVarInt(msg.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
ProtoVarInt(msg.message_type)
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
// Add iovec for this packet (header + payload)
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
this->reusable_iovs_.push_back({buf_start + header_offset, packet_len});
total_write_len += packet_len;
// Add iovec for this message (header + payload)
size_t msg_len = static_cast<size_t>(total_header_len + msg.payload_size);
iovs.push_back({buf_start + header_offset, msg_len});
total_write_len += msg_len;
}
// Send all packets in one writev call
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
// Send all messages in one writev call
return write_raw_(iovs.data(), iovs.size(), total_write_len);
}
} // namespace esphome::api

View File

@@ -7,8 +7,7 @@ namespace esphome::api {
class APIPlaintextFrameHelper final : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info) {
explicit APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
// Plaintext header structure (worst case):
// Pos 0: indicator (0x00)
// Pos 1-3: payload size varint (up to 3 bytes)
@@ -21,7 +20,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
protected:
APIError try_read_frame_();

File diff suppressed because it is too large Load Diff

View File

@@ -129,6 +129,25 @@ enum ClimatePreset : uint32_t {
CLIMATE_PRESET_ACTIVITY = 7,
};
#endif
#ifdef USE_WATER_HEATER
enum WaterHeaterMode : uint32_t {
WATER_HEATER_MODE_OFF = 0,
WATER_HEATER_MODE_ECO = 1,
WATER_HEATER_MODE_ELECTRIC = 2,
WATER_HEATER_MODE_PERFORMANCE = 3,
WATER_HEATER_MODE_HIGH_DEMAND = 4,
WATER_HEATER_MODE_HEAT_PUMP = 5,
WATER_HEATER_MODE_GAS = 6,
};
#endif
enum WaterHeaterCommandHasField : uint32_t {
WATER_HEATER_COMMAND_HAS_NONE = 0,
WATER_HEATER_COMMAND_HAS_MODE = 1,
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2,
WATER_HEATER_COMMAND_HAS_STATE = 4,
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8,
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16,
};
#ifdef USE_NUMBER
enum NumberMode : uint32_t {
NUMBER_MODE_AUTO = 0,
@@ -296,15 +315,12 @@ enum ZWaveProxyRequestType : uint32_t {
class InfoResponseProtoMessage : public ProtoMessage {
public:
~InfoResponseProtoMessage() override = default;
StringRef object_id_ref_{};
void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; }
StringRef object_id{};
uint32_t key{0};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef name{};
bool disabled_by_default{false};
#ifdef USE_ENTITY_ICON
StringRef icon_ref_{};
void set_icon(const StringRef &ref) { this->icon_ref_ = ref; }
StringRef icon{};
#endif
enums::EntityCategory entity_category{};
#ifdef USE_DEVICES
@@ -338,12 +354,11 @@ class CommandProtoMessage : public ProtoDecodableMessage {
class HelloRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 1;
static constexpr uint8_t ESTIMATED_SIZE = 27;
static constexpr uint8_t ESTIMATED_SIZE = 17;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "hello_request"; }
#endif
const uint8_t *client_info{nullptr};
uint16_t client_info_len{0};
StringRef client_info{};
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -363,10 +378,8 @@ class HelloResponse final : public ProtoMessage {
#endif
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
StringRef server_info_ref_{};
void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; }
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef server_info{};
StringRef name{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -375,40 +388,6 @@ class HelloResponse final : public ProtoMessage {
protected:
};
#ifdef USE_API_PASSWORD
class AuthenticationRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 3;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_request"; }
#endif
const uint8_t *password{nullptr};
uint16_t password_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class AuthenticationResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 4;
static constexpr uint8_t ESTIMATED_SIZE = 2;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_response"; }
#endif
bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
#endif
class DisconnectRequest final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 5;
@@ -478,8 +457,7 @@ class DeviceInfoRequest final : public ProtoMessage {
class AreaInfo final : public ProtoMessage {
public:
uint32_t area_id{0};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef name{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -493,8 +471,7 @@ class AreaInfo final : public ProtoMessage {
class DeviceInfo final : public ProtoMessage {
public:
uint32_t device_id{0};
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef name{};
uint32_t area_id{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -508,33 +485,23 @@ class DeviceInfo final : public ProtoMessage {
class DeviceInfoResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 10;
static constexpr uint16_t ESTIMATED_SIZE = 257;
static constexpr uint8_t ESTIMATED_SIZE = 255;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "device_info_response"; }
#endif
#ifdef USE_API_PASSWORD
bool uses_password{false};
#endif
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef mac_address_ref_{};
void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; }
StringRef esphome_version_ref_{};
void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; }
StringRef compilation_time_ref_{};
void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; }
StringRef model_ref_{};
void set_model(const StringRef &ref) { this->model_ref_ = ref; }
StringRef name{};
StringRef mac_address{};
StringRef esphome_version{};
StringRef compilation_time{};
StringRef model{};
#ifdef USE_DEEP_SLEEP
bool has_deep_sleep{false};
#endif
#ifdef ESPHOME_PROJECT_NAME
StringRef project_name_ref_{};
void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; }
StringRef project_name{};
#endif
#ifdef ESPHOME_PROJECT_NAME
StringRef project_version_ref_{};
void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; }
StringRef project_version{};
#endif
#ifdef USE_WEBSERVER
uint32_t webserver_port{0};
@@ -542,20 +509,16 @@ class DeviceInfoResponse final : public ProtoMessage {
#ifdef USE_BLUETOOTH_PROXY
uint32_t bluetooth_proxy_feature_flags{0};
#endif
StringRef manufacturer_ref_{};
void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; }
StringRef friendly_name_ref_{};
void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; }
StringRef manufacturer{};
StringRef friendly_name{};
#ifdef USE_VOICE_ASSISTANT
uint32_t voice_assistant_feature_flags{0};
#endif
#ifdef USE_AREAS
StringRef suggested_area_ref_{};
void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; }
StringRef suggested_area{};
#endif
#ifdef USE_BLUETOOTH_PROXY
StringRef bluetooth_mac_address_ref_{};
void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; }
StringRef bluetooth_mac_address{};
#endif
#ifdef USE_API_NOISE
bool api_encryption_supported{false};
@@ -630,8 +593,7 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_binary_sensor_response"; }
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -670,8 +632,7 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage {
bool assumed_state{false};
bool supports_position{false};
bool supports_tilt{false};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -752,8 +713,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
bool oscillating{false};
enums::FanDirection direction{};
int32_t speed_level{0};
StringRef preset_mode_ref_{};
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
StringRef preset_mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -778,7 +738,7 @@ class FanCommandRequest final : public CommandProtoMessage {
bool has_speed_level{false};
int32_t speed_level{0};
bool has_preset_mode{false};
std::string preset_mode{};
StringRef preset_mode{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -827,8 +787,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
float color_temperature{0.0f};
float cold_white{0.0f};
float warm_white{0.0f};
StringRef effect_ref_{};
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
StringRef effect{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -840,7 +799,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
class LightCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 32;
static constexpr uint8_t ESTIMATED_SIZE = 122;
static constexpr uint8_t ESTIMATED_SIZE = 112;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "light_command_request"; }
#endif
@@ -869,8 +828,7 @@ class LightCommandRequest final : public CommandProtoMessage {
bool has_flash_length{false};
uint32_t flash_length{0};
bool has_effect{false};
const uint8_t *effect{nullptr};
uint16_t effect_len{0};
StringRef effect{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -889,12 +847,10 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_sensor_response"; }
#endif
StringRef unit_of_measurement_ref_{};
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
StringRef unit_of_measurement{};
int32_t accuracy_decimals{0};
bool force_update{false};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -931,8 +887,7 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_switch_response"; }
#endif
bool assumed_state{false};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -982,8 +937,7 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_text_sensor_response"; }
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -999,8 +953,7 @@ class TextSensorStateResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_sensor_state_response"; }
#endif
StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
StringRef state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1030,7 +983,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
class SubscribeLogsResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 29;
static constexpr uint8_t ESTIMATED_SIZE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_logs_response"; }
#endif
@@ -1053,11 +1006,12 @@ class SubscribeLogsResponse final : public ProtoMessage {
class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 124;
static constexpr uint8_t ESTIMATED_SIZE = 9;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "noise_encryption_set_key_request"; }
#endif
std::string key{};
const uint8_t *key{nullptr};
uint16_t key_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1098,8 +1052,7 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
};
class HomeassistantServiceMap final : public ProtoMessage {
public:
StringRef key_ref_{};
void set_key(const StringRef &ref) { this->key_ref_ = ref; }
StringRef key{};
std::string value{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1116,8 +1069,7 @@ class HomeassistantActionRequest final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "homeassistant_action_request"; }
#endif
StringRef service_ref_{};
void set_service(const StringRef &ref) { this->service_ref_ = ref; }
StringRef service{};
FixedVector<HomeassistantServiceMap> data{};
FixedVector<HomeassistantServiceMap> data_template{};
FixedVector<HomeassistantServiceMap> variables{};
@@ -1150,7 +1102,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
#endif
uint32_t call_id{0};
bool success{false};
std::string error_message{};
StringRef error_message{};
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
uint16_t response_data_len{0};
@@ -1185,10 +1137,8 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_home_assistant_state_response"; }
#endif
StringRef entity_id_ref_{};
void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; }
StringRef attribute_ref_{};
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
StringRef entity_id{};
StringRef attribute{};
bool once{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1205,9 +1155,9 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "home_assistant_state_response"; }
#endif
std::string entity_id{};
std::string state{};
std::string attribute{};
StringRef entity_id{};
StringRef state{};
StringRef attribute{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1232,13 +1182,12 @@ class GetTimeRequest final : public ProtoMessage {
class GetTimeResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 37;
static constexpr uint8_t ESTIMATED_SIZE = 24;
static constexpr uint8_t ESTIMATED_SIZE = 14;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "get_time_response"; }
#endif
uint32_t epoch_seconds{0};
const uint8_t *timezone{nullptr};
uint16_t timezone_len{0};
StringRef timezone{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1250,8 +1199,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
#ifdef USE_API_USER_DEFINED_ACTIONS
class ListEntitiesServicesArgument final : public ProtoMessage {
public:
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef name{};
enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1268,8 +1216,7 @@ class ListEntitiesServicesResponse final : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_services_response"; }
#endif
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
StringRef name{};
uint32_t key{0};
FixedVector<ListEntitiesServicesArgument> args{};
enums::SupportsResponseType supports_response{};
@@ -1286,7 +1233,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
bool bool_{false};
int32_t legacy_int{0};
float float_{0.0f};
std::string string_{};
StringRef string_{};
int32_t int_{0};
FixedVector<bool> bool_array{};
FixedVector<int32_t> int_array{};
@@ -1338,8 +1285,7 @@ class ExecuteServiceResponse final : public ProtoMessage {
#endif
uint32_t call_id{0};
bool success{false};
StringRef error_message_ref_{};
void set_error_message(const StringRef &ref) { this->error_message_ref_ = ref; }
StringRef error_message{};
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
uint16_t response_data_len{0};
@@ -1372,7 +1318,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
class CameraImageResponse final : public StateResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 44;
static constexpr uint8_t ESTIMATED_SIZE = 20;
static constexpr uint8_t ESTIMATED_SIZE = 30;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "camera_image_response"; }
#endif
@@ -1457,11 +1403,9 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
StringRef custom_fan_mode_ref_{};
void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; }
StringRef custom_fan_mode{};
enums::ClimatePreset preset{};
StringRef custom_preset_ref_{};
void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; }
StringRef custom_preset{};
float current_humidity{0.0f};
float target_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
@@ -1492,11 +1436,11 @@ class ClimateCommandRequest final : public CommandProtoMessage {
bool has_swing_mode{false};
enums::ClimateSwingMode swing_mode{};
bool has_custom_fan_mode{false};
std::string custom_fan_mode{};
StringRef custom_fan_mode{};
bool has_preset{false};
enums::ClimatePreset preset{};
bool has_custom_preset{false};
std::string custom_preset{};
StringRef custom_preset{};
bool has_target_humidity{false};
float target_humidity{0.0f};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1509,6 +1453,70 @@ class ClimateCommandRequest final : public CommandProtoMessage {
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
#endif
#ifdef USE_WATER_HEATER
class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 132;
static constexpr uint8_t ESTIMATED_SIZE = 63;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_water_heater_response"; }
#endif
float min_temperature{0.0f};
float max_temperature{0.0f};
float target_temperature_step{0.0f};
const water_heater::WaterHeaterModeMask *supported_modes{};
uint32_t supported_features{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class WaterHeaterStateResponse final : public StateResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 133;
static constexpr uint8_t ESTIMATED_SIZE = 35;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "water_heater_state_response"; }
#endif
float current_temperature{0.0f};
float target_temperature{0.0f};
enums::WaterHeaterMode mode{};
uint32_t state{0};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class WaterHeaterCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 134;
static constexpr uint8_t ESTIMATED_SIZE = 34;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "water_heater_command_request"; }
#endif
uint32_t has_fields{0};
enums::WaterHeaterMode mode{};
float target_temperature{0.0f};
uint32_t state{0};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
#endif
#ifdef USE_NUMBER
class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
public:
@@ -1520,11 +1528,9 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
float min_value{0.0f};
float max_value{0.0f};
float step{0.0f};
StringRef unit_of_measurement_ref_{};
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
StringRef unit_of_measurement{};
enums::NumberMode mode{};
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1591,8 +1597,7 @@ class SelectStateResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_state_response"; }
#endif
StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
StringRef state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1605,12 +1610,11 @@ class SelectStateResponse final : public StateResponseProtoMessage {
class SelectCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 54;
static constexpr uint8_t ESTIMATED_SIZE = 28;
static constexpr uint8_t ESTIMATED_SIZE = 18;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_command_request"; }
#endif
const uint8_t *state{nullptr};
uint16_t state_len{0};
StringRef state{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1629,7 +1633,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_siren_response"; }
#endif
std::vector<std::string> tones{};
const FixedVector<const char *> *tones{};
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -1666,7 +1670,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
bool has_state{false};
bool state{false};
bool has_tone{false};
std::string tone{};
StringRef tone{};
bool has_duration{false};
uint32_t duration{0};
bool has_volume{false};
@@ -1692,8 +1696,7 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage {
bool assumed_state{false};
bool supports_open{false};
bool requires_code{false};
StringRef code_format_ref_{};
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
StringRef code_format{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1727,7 +1730,7 @@ class LockCommandRequest final : public CommandProtoMessage {
#endif
enums::LockCommand command{};
bool has_code{false};
std::string code{};
StringRef code{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1746,8 +1749,7 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_button_response"; }
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1775,8 +1777,7 @@ class ButtonCommandRequest final : public CommandProtoMessage {
#ifdef USE_MEDIA_PLAYER
class MediaPlayerSupportedFormat final : public ProtoMessage {
public:
StringRef format_ref_{};
void set_format(const StringRef &ref) { this->format_ref_ = ref; }
StringRef format{};
uint32_t sample_rate{0};
uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{};
@@ -1837,7 +1838,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
bool has_volume{false};
float volume{0.0f};
bool has_media_url{false};
std::string media_url{};
StringRef media_url{};
bool has_announcement{false};
bool announcement{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2044,7 +2045,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
class BluetoothGATTReadResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 74;
static constexpr uint8_t ESTIMATED_SIZE = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_read_response"; }
#endif
@@ -2139,7 +2140,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 79;
static constexpr uint8_t ESTIMATED_SIZE = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; }
#endif
@@ -2381,12 +2382,10 @@ class VoiceAssistantRequest final : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_request"; }
#endif
bool start{false};
StringRef conversation_id_ref_{};
void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; }
StringRef conversation_id{};
uint32_t flags{0};
VoiceAssistantAudioSettings audio_settings{};
StringRef wake_word_phrase_ref_{};
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
StringRef wake_word_phrase{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2413,8 +2412,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
};
class VoiceAssistantEventData final : public ProtoDecodableMessage {
public:
std::string name{};
std::string value{};
StringRef name{};
StringRef value{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2442,17 +2441,12 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
class VoiceAssistantAudio final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 106;
static constexpr uint8_t ESTIMATED_SIZE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_audio"; }
#endif
std::string data{};
const uint8_t *data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
const uint8_t *data{nullptr};
uint16_t data_len{0};
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2472,8 +2466,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
const char *message_name() const override { return "voice_assistant_timer_event_response"; }
#endif
enums::VoiceAssistantTimerEvent event_type{};
std::string timer_id{};
std::string name{};
StringRef timer_id{};
StringRef name{};
uint32_t total_seconds{0};
uint32_t seconds_left{0};
bool is_active{false};
@@ -2492,9 +2486,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_announce_request"; }
#endif
std::string media_id{};
std::string text{};
std::string preannounce_media_id{};
StringRef media_id{};
StringRef text{};
StringRef preannounce_media_id{};
bool start_conversation{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -2522,10 +2516,8 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage {
};
class VoiceAssistantWakeWord final : public ProtoMessage {
public:
StringRef id_ref_{};
void set_id(const StringRef &ref) { this->id_ref_ = ref; }
StringRef wake_word_ref_{};
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
StringRef id{};
StringRef wake_word{};
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2537,13 +2529,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
};
class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
public:
std::string id{};
std::string wake_word{};
StringRef id{};
StringRef wake_word{};
std::vector<std::string> trained_languages{};
std::string model_type{};
StringRef model_type{};
uint32_t model_size{0};
std::string model_hash{};
std::string url{};
StringRef model_hash{};
StringRef url{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2644,7 +2636,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
const char *message_name() const override { return "alarm_control_panel_command_request"; }
#endif
enums::AlarmControlPanelStateCommand command{};
std::string code{};
StringRef code{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2665,8 +2657,7 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage {
#endif
uint32_t min_length{0};
uint32_t max_length{0};
StringRef pattern_ref_{};
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
StringRef pattern{};
enums::TextMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2683,8 +2674,7 @@ class TextStateResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_state_response"; }
#endif
StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
StringRef state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2701,7 +2691,7 @@ class TextCommandRequest final : public CommandProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_command_request"; }
#endif
std::string state{};
StringRef state{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2828,8 +2818,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_event_response"; }
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
const FixedVector<const char *> *event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2846,8 +2835,7 @@ class EventResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "event_response"; }
#endif
StringRef event_type_ref_{};
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
StringRef event_type{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2865,8 +2853,7 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_valve_response"; }
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
bool assumed_state{false};
bool supports_position{false};
bool supports_stop{false};
@@ -2972,8 +2959,7 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_update_response"; }
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
StringRef device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2993,16 +2979,11 @@ class UpdateStateResponse final : public StateResponseProtoMessage {
bool in_progress{false};
bool has_progress{false};
float progress{0.0f};
StringRef current_version_ref_{};
void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; }
StringRef latest_version_ref_{};
void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; }
StringRef title_ref_{};
void set_title(const StringRef &ref) { this->title_ref_ = ref; }
StringRef release_summary_ref_{};
void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; }
StringRef release_url_ref_{};
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
StringRef current_version{};
StringRef latest_version{};
StringRef title{};
StringRef release_summary{};
StringRef release_url{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -348,6 +348,47 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
}
}
#endif
#ifdef USE_WATER_HEATER
template<> const char *proto_enum_to_string<enums::WaterHeaterMode>(enums::WaterHeaterMode value) {
switch (value) {
case enums::WATER_HEATER_MODE_OFF:
return "WATER_HEATER_MODE_OFF";
case enums::WATER_HEATER_MODE_ECO:
return "WATER_HEATER_MODE_ECO";
case enums::WATER_HEATER_MODE_ELECTRIC:
return "WATER_HEATER_MODE_ELECTRIC";
case enums::WATER_HEATER_MODE_PERFORMANCE:
return "WATER_HEATER_MODE_PERFORMANCE";
case enums::WATER_HEATER_MODE_HIGH_DEMAND:
return "WATER_HEATER_MODE_HIGH_DEMAND";
case enums::WATER_HEATER_MODE_HEAT_PUMP:
return "WATER_HEATER_MODE_HEAT_PUMP";
case enums::WATER_HEATER_MODE_GAS:
return "WATER_HEATER_MODE_GAS";
default:
return "UNKNOWN";
}
}
#endif
template<>
const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::WaterHeaterCommandHasField value) {
switch (value) {
case enums::WATER_HEATER_COMMAND_HAS_NONE:
return "WATER_HEATER_COMMAND_HAS_NONE";
case enums::WATER_HEATER_COMMAND_HAS_MODE:
return "WATER_HEATER_COMMAND_HAS_MODE";
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE:
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE";
case enums::WATER_HEATER_COMMAND_HAS_STATE:
return "WATER_HEATER_COMMAND_HAS_STATE";
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW:
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW";
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH:
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH";
default:
return "UNKNOWN";
}
}
#ifdef USE_NUMBER
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
switch (value) {
@@ -694,9 +735,7 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest");
out.append(" client_info: ");
out.append(format_hex_pretty(this->client_info, this->client_info_len));
out.append("\n");
dump_field(out, "client_info", this->client_info);
dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor);
}
@@ -704,21 +743,9 @@ void HelloResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloResponse");
dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor);
dump_field(out, "server_info", this->server_info_ref_);
dump_field(out, "name", this->name_ref_);
dump_field(out, "server_info", this->server_info);
dump_field(out, "name", this->name);
}
#ifdef USE_API_PASSWORD
void AuthenticationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationRequest");
out.append(" password: ");
out.append(format_hex_pretty(this->password, this->password_len));
out.append("\n");
}
void AuthenticationResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationResponse");
dump_field(out, "invalid_password", this->invalid_password);
}
#endif
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
@@ -728,35 +755,32 @@ void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfo
void AreaInfo::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AreaInfo");
dump_field(out, "area_id", this->area_id);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
}
#endif
#ifdef USE_DEVICES
void DeviceInfo::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "DeviceInfo");
dump_field(out, "device_id", this->device_id);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "area_id", this->area_id);
}
#endif
void DeviceInfoResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "DeviceInfoResponse");
#ifdef USE_API_PASSWORD
dump_field(out, "uses_password", this->uses_password);
#endif
dump_field(out, "name", this->name_ref_);
dump_field(out, "mac_address", this->mac_address_ref_);
dump_field(out, "esphome_version", this->esphome_version_ref_);
dump_field(out, "compilation_time", this->compilation_time_ref_);
dump_field(out, "model", this->model_ref_);
dump_field(out, "name", this->name);
dump_field(out, "mac_address", this->mac_address);
dump_field(out, "esphome_version", this->esphome_version);
dump_field(out, "compilation_time", this->compilation_time);
dump_field(out, "model", this->model);
#ifdef USE_DEEP_SLEEP
dump_field(out, "has_deep_sleep", this->has_deep_sleep);
#endif
#ifdef ESPHOME_PROJECT_NAME
dump_field(out, "project_name", this->project_name_ref_);
dump_field(out, "project_name", this->project_name);
#endif
#ifdef ESPHOME_PROJECT_NAME
dump_field(out, "project_version", this->project_version_ref_);
dump_field(out, "project_version", this->project_version);
#endif
#ifdef USE_WEBSERVER
dump_field(out, "webserver_port", this->webserver_port);
@@ -764,16 +788,16 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
#ifdef USE_BLUETOOTH_PROXY
dump_field(out, "bluetooth_proxy_feature_flags", this->bluetooth_proxy_feature_flags);
#endif
dump_field(out, "manufacturer", this->manufacturer_ref_);
dump_field(out, "friendly_name", this->friendly_name_ref_);
dump_field(out, "manufacturer", this->manufacturer);
dump_field(out, "friendly_name", this->friendly_name);
#ifdef USE_VOICE_ASSISTANT
dump_field(out, "voice_assistant_feature_flags", this->voice_assistant_feature_flags);
#endif
#ifdef USE_AREAS
dump_field(out, "suggested_area", this->suggested_area_ref_);
dump_field(out, "suggested_area", this->suggested_area);
#endif
#ifdef USE_BLUETOOTH_PROXY
dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address_ref_);
dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address);
#endif
#ifdef USE_API_NOISE
dump_field(out, "api_encryption_supported", this->api_encryption_supported);
@@ -810,14 +834,14 @@ void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("Subsc
#ifdef USE_BINARY_SENSOR
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "name", this->name);
dump_field(out, "device_class", this->device_class);
dump_field(out, "is_status_binary_sensor", this->is_status_binary_sensor);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
@@ -837,16 +861,16 @@ void BinarySensorStateResponse::dump_to(std::string &out) const {
#ifdef USE_COVER
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesCoverResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "supports_position", this->supports_position);
dump_field(out, "supports_tilt", this->supports_tilt);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "supports_stop", this->supports_stop);
@@ -880,16 +904,16 @@ void CoverCommandRequest::dump_to(std::string &out) const {
#ifdef USE_FAN
void ListEntitiesFanResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesFanResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "supports_oscillation", this->supports_oscillation);
dump_field(out, "supports_speed", this->supports_speed);
dump_field(out, "supports_direction", this->supports_direction);
dump_field(out, "supported_speed_count", this->supported_speed_count);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
for (const auto &it : *this->supported_preset_modes) {
@@ -906,7 +930,7 @@ void FanStateResponse::dump_to(std::string &out) const {
dump_field(out, "oscillating", this->oscillating);
dump_field(out, "direction", static_cast<enums::FanDirection>(this->direction));
dump_field(out, "speed_level", this->speed_level);
dump_field(out, "preset_mode", this->preset_mode_ref_);
dump_field(out, "preset_mode", this->preset_mode);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -932,9 +956,9 @@ void FanCommandRequest::dump_to(std::string &out) const {
#ifdef USE_LIGHT
void ListEntitiesLightResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesLightResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
for (const auto &it : *this->supported_color_modes) {
dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4);
}
@@ -945,7 +969,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
@@ -966,7 +990,7 @@ void LightStateResponse::dump_to(std::string &out) const {
dump_field(out, "color_temperature", this->color_temperature);
dump_field(out, "cold_white", this->cold_white);
dump_field(out, "warm_white", this->warm_white);
dump_field(out, "effect", this->effect_ref_);
dump_field(out, "effect", this->effect);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -999,9 +1023,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_flash_length", this->has_flash_length);
dump_field(out, "flash_length", this->flash_length);
dump_field(out, "has_effect", this->has_effect);
out.append(" effect: ");
out.append(format_hex_pretty(this->effect, this->effect_len));
out.append("\n");
dump_field(out, "effect", this->effect);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1010,16 +1032,16 @@ void LightCommandRequest::dump_to(std::string &out) const {
#ifdef USE_SENSOR
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSensorResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_);
dump_field(out, "unit_of_measurement", this->unit_of_measurement);
dump_field(out, "accuracy_decimals", this->accuracy_decimals);
dump_field(out, "force_update", this->force_update);
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
dump_field(out, "state_class", static_cast<enums::SensorStateClass>(this->state_class));
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -1040,16 +1062,16 @@ void SensorStateResponse::dump_to(std::string &out) const {
#ifdef USE_SWITCH
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSwitchResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1074,15 +1096,15 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
#ifdef USE_TEXT_SENSOR
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesTextSensorResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1090,7 +1112,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
void TextSensorStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextSensorStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state_ref_);
dump_field(out, "state", this->state);
dump_field(out, "missing_state", this->missing_state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1113,10 +1135,13 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
out.append(" key: ");
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->key.data()), this->key.size()));
out.append(format_hex_pretty(this->key, this->key_len));
out.append("\n");
}
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse");
dump_field(out, "success", this->success);
}
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
@@ -1124,12 +1149,12 @@ void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
}
void HomeassistantServiceMap::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceMap");
dump_field(out, "key", this->key_ref_);
dump_field(out, "key", this->key);
dump_field(out, "value", this->value);
}
void HomeassistantActionRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantActionRequest");
dump_field(out, "service", this->service_ref_);
dump_field(out, "service", this->service);
for (const auto &it : this->data) {
out.append(" data: ");
it.dump_to(out);
@@ -1176,8 +1201,8 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
}
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
dump_field(out, "entity_id", this->entity_id_ref_);
dump_field(out, "attribute", this->attribute_ref_);
dump_field(out, "entity_id", this->entity_id);
dump_field(out, "attribute", this->attribute);
dump_field(out, "once", this->once);
}
void HomeAssistantStateResponse::dump_to(std::string &out) const {
@@ -1191,19 +1216,17 @@ void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeReques
void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: ");
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("\n");
dump_field(out, "timezone", this->timezone);
}
#ifdef USE_API_USER_DEFINED_ACTIONS
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "type", static_cast<enums::ServiceArgType>(this->type));
}
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesResponse");
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "key", this->key);
for (const auto &it : this->args) {
out.append(" args: ");
@@ -1253,7 +1276,7 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ExecuteServiceResponse");
dump_field(out, "call_id", this->call_id);
dump_field(out, "success", this->success);
dump_field(out, "error_message", this->error_message_ref_);
dump_field(out, "error_message", this->error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
out.append(" response_data: ");
out.append(format_hex_pretty(this->response_data, this->response_data_len));
@@ -1264,12 +1287,12 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
#ifdef USE_CAMERA
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesCameraResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
@@ -1296,9 +1319,9 @@ void CameraImageRequest::dump_to(std::string &out) const {
#ifdef USE_CLIMATE
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesClimateResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
dump_field(out, "supports_current_temperature", this->supports_current_temperature);
dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature);
for (const auto &it : *this->supported_modes) {
@@ -1325,7 +1348,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "visual_current_temperature_step", this->visual_current_temperature_step);
@@ -1349,9 +1372,9 @@ void ClimateStateResponse::dump_to(std::string &out) const {
dump_field(out, "action", static_cast<enums::ClimateAction>(this->action));
dump_field(out, "fan_mode", static_cast<enums::ClimateFanMode>(this->fan_mode));
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
dump_field(out, "custom_fan_mode", this->custom_fan_mode_ref_);
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
dump_field(out, "custom_preset", this->custom_preset_ref_);
dump_field(out, "custom_preset", this->custom_preset);
dump_field(out, "current_humidity", this->current_humidity);
dump_field(out, "target_humidity", this->target_humidity);
#ifdef USE_DEVICES
@@ -1386,23 +1409,72 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
#endif
}
#endif
#ifdef USE_WATER_HEATER
void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse");
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "min_temperature", this->min_temperature);
dump_field(out, "max_temperature", this->max_temperature);
dump_field(out, "target_temperature_step", this->target_temperature_step);
for (const auto &it : *this->supported_modes) {
dump_field(out, "supported_modes", static_cast<enums::WaterHeaterMode>(it), 4);
}
dump_field(out, "supported_features", this->supported_features);
}
void WaterHeaterStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "WaterHeaterStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "current_temperature", this->current_temperature);
dump_field(out, "target_temperature", this->target_temperature);
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "state", this->state);
dump_field(out, "target_temperature_low", this->target_temperature_low);
dump_field(out, "target_temperature_high", this->target_temperature_high);
}
void WaterHeaterCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "WaterHeaterCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "has_fields", this->has_fields);
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
dump_field(out, "target_temperature", this->target_temperature);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "state", this->state);
dump_field(out, "target_temperature_low", this->target_temperature_low);
dump_field(out, "target_temperature_high", this->target_temperature_high);
}
#endif
#ifdef USE_NUMBER
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesNumberResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "min_value", this->min_value);
dump_field(out, "max_value", this->max_value);
dump_field(out, "step", this->step);
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_);
dump_field(out, "unit_of_measurement", this->unit_of_measurement);
dump_field(out, "mode", static_cast<enums::NumberMode>(this->mode));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1428,11 +1500,11 @@ void NumberCommandRequest::dump_to(std::string &out) const {
#ifdef USE_SELECT
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSelectResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
for (const auto &it : *this->options) {
dump_field(out, "options", it, 4);
@@ -1446,7 +1518,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
void SelectStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state_ref_);
dump_field(out, "state", this->state);
dump_field(out, "missing_state", this->missing_state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1455,9 +1527,7 @@ void SelectStateResponse::dump_to(std::string &out) const {
void SelectCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectCommandRequest");
dump_field(out, "key", this->key);
out.append(" state: ");
out.append(format_hex_pretty(this->state, this->state_len));
out.append("\n");
dump_field(out, "state", this->state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1466,14 +1536,14 @@ void SelectCommandRequest::dump_to(std::string &out) const {
#ifdef USE_SIREN
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesSirenResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
for (const auto &it : this->tones) {
for (const auto &it : *this->tones) {
dump_field(out, "tones", it, 4);
}
dump_field(out, "supports_duration", this->supports_duration);
@@ -1510,18 +1580,18 @@ void SirenCommandRequest::dump_to(std::string &out) const {
#ifdef USE_LOCK
void ListEntitiesLockResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesLockResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "supports_open", this->supports_open);
dump_field(out, "requires_code", this->requires_code);
dump_field(out, "code_format", this->code_format_ref_);
dump_field(out, "code_format", this->code_format);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1548,15 +1618,15 @@ void LockCommandRequest::dump_to(std::string &out) const {
#ifdef USE_BUTTON
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesButtonResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1572,7 +1642,7 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
#ifdef USE_MEDIA_PLAYER
void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "MediaPlayerSupportedFormat");
dump_field(out, "format", this->format_ref_);
dump_field(out, "format", this->format);
dump_field(out, "sample_rate", this->sample_rate);
dump_field(out, "num_channels", this->num_channels);
dump_field(out, "purpose", static_cast<enums::MediaPlayerFormatPurpose>(this->purpose));
@@ -1580,11 +1650,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
}
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesMediaPlayerResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -1661,7 +1731,10 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
dump_field(out, "mtu", this->mtu);
dump_field(out, "error", this->error);
}
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { dump_field(out, "address", this->address); }
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTGetServicesRequest");
dump_field(out, "address", this->address);
}
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTDescriptor");
for (const auto &it : this->uuid) {
@@ -1833,12 +1906,12 @@ void VoiceAssistantAudioSettings::dump_to(std::string &out) const {
void VoiceAssistantRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantRequest");
dump_field(out, "start", this->start);
dump_field(out, "conversation_id", this->conversation_id_ref_);
dump_field(out, "conversation_id", this->conversation_id);
dump_field(out, "flags", this->flags);
out.append(" audio_settings: ");
this->audio_settings.dump_to(out);
out.append("\n");
dump_field(out, "wake_word_phrase", this->wake_word_phrase_ref_);
dump_field(out, "wake_word_phrase", this->wake_word_phrase);
}
void VoiceAssistantResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantResponse");
@@ -1862,11 +1935,7 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
void VoiceAssistantAudio::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAudio");
out.append(" data: ");
if (this->data_ptr_ != nullptr) {
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
} else {
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
}
out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n");
dump_field(out, "end", this->end);
}
@@ -1886,11 +1955,14 @@ void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
dump_field(out, "preannounce_media_id", this->preannounce_media_id);
dump_field(out, "start_conversation", this->start_conversation);
}
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAnnounceFinished");
dump_field(out, "success", this->success);
}
void VoiceAssistantWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantWakeWord");
dump_field(out, "id", this->id_ref_);
dump_field(out, "wake_word", this->wake_word_ref_);
dump_field(out, "id", this->id);
dump_field(out, "wake_word", this->wake_word);
for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4);
}
@@ -1937,11 +2009,11 @@ void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
#ifdef USE_ALARM_CONTROL_PANEL
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesAlarmControlPanelResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -1973,17 +2045,17 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
#ifdef USE_TEXT
void ListEntitiesTextResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesTextResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "min_length", this->min_length);
dump_field(out, "max_length", this->max_length);
dump_field(out, "pattern", this->pattern_ref_);
dump_field(out, "pattern", this->pattern);
dump_field(out, "mode", static_cast<enums::TextMode>(this->mode));
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1992,7 +2064,7 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
void TextStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextStateResponse");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state_ref_);
dump_field(out, "state", this->state);
dump_field(out, "missing_state", this->missing_state);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -2010,11 +2082,11 @@ void TextCommandRequest::dump_to(std::string &out) const {
#ifdef USE_DATETIME_DATE
void ListEntitiesDateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesDateResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2047,11 +2119,11 @@ void DateCommandRequest::dump_to(std::string &out) const {
#ifdef USE_DATETIME_TIME
void ListEntitiesTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesTimeResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2084,15 +2156,15 @@ void TimeCommandRequest::dump_to(std::string &out) const {
#ifdef USE_EVENT
void ListEntitiesEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesEventResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
for (const auto &it : *this->event_types) {
dump_field(out, "event_types", it, 4);
}
@@ -2103,7 +2175,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
void EventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "EventResponse");
dump_field(out, "key", this->key);
dump_field(out, "event_type", this->event_type_ref_);
dump_field(out, "event_type", this->event_type);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2112,15 +2184,15 @@ void EventResponse::dump_to(std::string &out) const {
#ifdef USE_VALVE
void ListEntitiesValveResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesValveResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
dump_field(out, "assumed_state", this->assumed_state);
dump_field(out, "supports_position", this->supports_position);
dump_field(out, "supports_stop", this->supports_stop);
@@ -2151,11 +2223,11 @@ void ValveCommandRequest::dump_to(std::string &out) const {
#ifdef USE_DATETIME_DATETIME
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesDateTimeResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
@@ -2184,15 +2256,15 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
#ifdef USE_UPDATE
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesUpdateResponse");
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "object_id", this->object_id);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
dump_field(out, "name", this->name);
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
dump_field(out, "icon", this->icon);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
dump_field(out, "device_class", this->device_class);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2204,11 +2276,11 @@ void UpdateStateResponse::dump_to(std::string &out) const {
dump_field(out, "in_progress", this->in_progress);
dump_field(out, "has_progress", this->has_progress);
dump_field(out, "progress", this->progress);
dump_field(out, "current_version", this->current_version_ref_);
dump_field(out, "latest_version", this->latest_version_ref_);
dump_field(out, "title", this->title_ref_);
dump_field(out, "release_summary", this->release_summary_ref_);
dump_field(out, "release_url", this->release_url_ref_);
dump_field(out, "current_version", this->current_version);
dump_field(out, "latest_version", this->latest_version);
dump_field(out, "title", this->title);
dump_field(out, "release_summary", this->release_summary);
dump_field(out, "release_url", this->release_url);
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif

View File

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

View File

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

View File

@@ -26,10 +26,6 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_hello_request(const HelloRequest &value){};
#ifdef USE_API_PASSWORD
virtual void on_authentication_request(const AuthenticationRequest &value){};
#endif
virtual void on_disconnect_request(const DisconnectRequest &value){};
virtual void on_disconnect_response(const DisconnectResponse &value){};
virtual void on_ping_request(const PingRequest &value){};
@@ -91,6 +87,10 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
#ifdef USE_WATER_HEATER
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
#endif
#ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif
@@ -224,9 +224,6 @@ class APIServerConnectionBase : public ProtoService {
class APIServerConnection : public APIServerConnectionBase {
public:
virtual bool send_hello_response(const HelloRequest &msg) = 0;
#ifdef USE_API_PASSWORD
virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
#endif
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
virtual bool send_ping_response(const PingRequest &msg) = 0;
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
@@ -353,9 +350,6 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
#ifdef USE_API_PASSWORD
void on_authentication_request(const AuthenticationRequest &msg) override;
#endif
void on_disconnect_request(const DisconnectRequest &msg) override;
void on_ping_request(const PingRequest &msg) override;
void on_device_info_request(const DeviceInfoRequest &msg) override;

View File

@@ -125,15 +125,18 @@ void APIServer::loop() {
if (!sock)
break;
char peername[socket::SOCKADDR_STR_LEN];
sock->getpeername_to(peername);
// Check if we're at the connection limit
if (this->clients_.size() >= this->max_connections_) {
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
// Immediately close - socket destructor will handle cleanup
sock.reset();
continue;
}
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
ESP_LOGD(TAG, "Accept %s", peername);
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
@@ -166,8 +169,7 @@ void APIServer::loop() {
// Network is down - disconnect all clients
for (auto &client : this->clients_) {
client->on_fatal_error();
ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
client->client_info_.peername.c_str());
client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect"));
}
// Continue to process and clean up the clients below
}
@@ -185,12 +187,12 @@ void APIServer::loop() {
// Rare case: handle disconnection
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername()));
#endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
this->unregister_active_action_calls_for_connection(client.get());
#endif
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str());
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
// Swap with the last element and pop (avoids expensive vector shifts)
if (client_index < this->clients_.size() - 1) {
@@ -224,38 +226,6 @@ void APIServer::dump_config() {
#endif
}
#ifdef USE_API_PASSWORD
bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
// depend only on input password length
const char *a = this->password_.c_str();
uint32_t len_a = this->password_.length();
const char *b = reinterpret_cast<const char *>(password_data);
uint32_t len_b = password_len;
// disable optimization with volatile
volatile uint32_t length = len_b;
volatile const char *left = nullptr;
volatile const char *right = b;
uint8_t result = 0;
if (len_a == length) {
left = *((volatile const char **) &a);
result = 0;
}
if (len_a != length) {
left = b;
result = 1;
}
for (size_t i = 0; i < length; i++) {
result |= *left++ ^ *right++; // NOLINT
}
return result == 0;
}
#endif
void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for controller update dispatch
@@ -335,6 +305,10 @@ API_DISPATCH_UPDATE(valve::Valve, valve)
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
#endif
#ifdef USE_WATER_HEATER
API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
#endif
#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()
@@ -373,10 +347,6 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI;
void APIServer::set_port(uint16_t port) { this->port_ = port; }
#ifdef USE_API_PASSWORD
void APIServer::set_password(const std::string &password) { this->password_ = password; }
#endif
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
#ifdef USE_API_HOMEASSISTANT_SERVICES
@@ -390,7 +360,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon
this->action_response_callbacks_.push_back({call_id, std::move(callback)});
}
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) {
auto callback = std::move(it->callback);
@@ -402,7 +372,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
}
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) {
@@ -420,8 +390,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper to add subscription (reduces duplication)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
std::function<void(std::string)> f, bool once) {
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
@@ -430,7 +400,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri
// Helper to add subscription with heap-allocated strings (reduces duplication)
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f, bool once) {
std::function<void(StringRef)> f, bool once) {
HomeAssistantStateSubscription sub;
// Allocate heap storage for the strings
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
@@ -450,23 +420,43 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
// New const char* overload (for internal components - zero allocation)
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
}
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
}
// Existing std::string overload (for custom_api_device.h - heap allocation)
// std::string overload with StringRef callback (zero-allocation callback)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
std::function<void(StringRef)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
// Legacy helper: wraps std::string callback and delegates to StringRef version
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once) {
// Wrap callback to convert StringRef -> std::string, then delegate
this->add_state_subscription_(std::move(entity_id), std::move(attribute),
std::function<void(StringRef)>([f = std::move(f)](StringRef state) { f(state.str()); }),
once);
}
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
@@ -674,7 +664,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
}
}
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) {
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {
call.connection->send_execute_service_response(call.client_call_id, success, error_message);
@@ -684,7 +674,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons
ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {

View File

@@ -10,6 +10,7 @@
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include "list_entities.h"
#include "subscribe_state.h"
#ifdef USE_LOGGER
@@ -59,10 +60,6 @@ class APIServer : public Component,
#endif
#ifdef USE_CAMERA
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
#endif
#ifdef USE_API_PASSWORD
bool check_password(const uint8_t *password_data, size_t password_len) const;
void set_password(const std::string &password);
#endif
void set_port(uint16_t port);
void set_reboot_timeout(uint32_t reboot_timeout);
@@ -133,6 +130,9 @@ class APIServer : public Component,
#ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif
#ifdef USE_WATER_HEATER
void on_water_heater_update(water_heater::WaterHeater *obj) override;
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_action(const HomeassistantActionRequest &call);
@@ -140,10 +140,10 @@ class APIServer : public Component,
// Action response handling
using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message);
void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len);
void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
size_t response_data_len);
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
@@ -162,9 +162,9 @@ class APIServer : public Component,
void unregister_active_action_call(uint32_t action_call_id);
void unregister_active_action_calls_for_connection(APIConnection *conn);
// Send response for a specific action call (uses action_call_id, sends client_call_id in response)
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message);
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -192,7 +192,7 @@ class APIServer : public Component,
struct HomeAssistantStateSubscription {
const char *entity_id; // Pointer to flash (internal) or heap (external)
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
std::function<void(std::string)> callback;
std::function<void(StringRef)> callback;
bool once;
// Dynamic storage for external components using std::string API (custom_api_device.h)
@@ -202,14 +202,20 @@ class APIServer : public Component,
};
// New const char* overload (for internal components - zero allocation)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
// Existing std::string overload (for custom_api_device.h - heap allocation)
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
std::function<void(StringRef)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
std::function<void(StringRef)> f);
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
@@ -233,10 +239,13 @@ class APIServer : public Component,
#endif // USE_API_NOISE
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper methods to reduce code duplication
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(std::string)> f,
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute, std::function<void(StringRef)> f,
bool once);
// Legacy helper: wraps std::string callback and delegates to StringRef version
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f, bool once);
std::function<void(const std::string &)> f, bool once);
#endif // USE_API_HOMEASSISTANT_STATES
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
@@ -253,9 +262,6 @@ class APIServer : public Component,
// Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_;
#ifdef USE_API_PASSWORD
std::string password_;
#endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
#ifdef USE_API_HOMEASSISTANT_STATES
std::vector<HomeAssistantStateSubscription> state_subs_;

View File

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

View File

@@ -122,21 +122,36 @@ class CustomAPIDevice {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
* }
*
* void on_state_changed(std::string state) {
* // State of sensor.weather_forecast is `state`
* void on_state_changed(StringRef state) {
* // State of climate.kitchen current_temperature is `state`
* // Use state.c_str() for C string, state.str() for std::string
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param callback The member function to call when the entity state changes (zero-allocation).
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
*
* @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0.
*/
template<typename T>
ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
// Explicit type to disambiguate overload resolution
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
std::function<void(const std::string &)>(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
@@ -148,23 +163,45 @@ class CustomAPIDevice {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* }
*
* void on_state_changed(std::string entity_id, std::string state) {
* void on_state_changed(const std::string &entity_id, StringRef state) {
* // State of `entity_id` is `state`
* }
* ```
*
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param callback The member function to call when the entity state changes (zero-allocation for state).
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
}
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
*
* @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0.
*/
template<typename T>
ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
// Explicit type to disambiguate overload resolution
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
std::function<void(const std::string &)>(f));
}
#else
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
@@ -173,6 +210,14 @@ class CustomAPIDevice {
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
@@ -195,7 +240,7 @@ class CustomAPIDevice {
*/
void call_homeassistant_service(const std::string &service_name) {
HomeassistantActionRequest resp;
resp.set_service(StringRef(service_name));
resp.service = StringRef(service_name);
global_api_server->send_homeassistant_action(resp);
}
@@ -215,12 +260,12 @@ class CustomAPIDevice {
*/
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantActionRequest resp;
resp.set_service(StringRef(service_name));
resp.service = StringRef(service_name);
resp.data.init(data.size());
for (auto &it : data) {
auto &kv = resp.data.emplace_back();
kv.set_key(StringRef(it.first));
kv.value = it.second;
kv.key = StringRef(it.first);
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
}
global_api_server->send_homeassistant_action(resp);
}
@@ -237,7 +282,7 @@ class CustomAPIDevice {
*/
void fire_homeassistant_event(const std::string &event_name) {
HomeassistantActionRequest resp;
resp.set_service(StringRef(event_name));
resp.service = StringRef(event_name);
resp.is_event = true;
global_api_server->send_homeassistant_action(resp);
}
@@ -257,13 +302,13 @@ class CustomAPIDevice {
*/
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantActionRequest resp;
resp.set_service(StringRef(service_name));
resp.service = StringRef(service_name);
resp.is_event = true;
resp.data.init(data.size());
for (auto &it : data) {
auto &kv = resp.data.emplace_back();
kv.set_key(StringRef(it.first));
kv.value = it.second;
kv.key = StringRef(it.first);
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
}
global_api_server->send_homeassistant_action(resp);
}

View File

@@ -67,10 +67,10 @@ template<typename... Ts> class TemplatableKeyValuePair {
// the callback is invoked synchronously while the message is on the stack).
class ActionResponse {
public:
ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {}
ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
: success_(success), error_message_(error_message) {
if (data == nullptr || data_len == 0)
return;
@@ -147,7 +147,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void play(const Ts &...x) override {
HomeassistantActionRequest resp;
std::string service_value = this->service_.value(x...);
resp.set_service(StringRef(service_value));
resp.service = StringRef(service_value);
resp.is_event = this->flags_.is_event;
this->populate_service_map(resp.data, this->data_, x...);
this->populate_service_map(resp.data_template, this->data_template_, x...);
@@ -209,7 +209,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
dest.init(source.size());
for (auto &it : source) {
auto &kv = dest.emplace_back();
kv.set_key(StringRef(it.key));
kv.key = StringRef(it.key);
kv.value = it.value.value(x...);
}
}

View File

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

View File

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

View File

@@ -54,16 +54,16 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) {
* 3. Global/static strings: StringRef(GLOBAL_CONSTANT) - Always safe
* 4. Local variables: Safe ONLY if encoding happens before function returns:
* std::string temp = compute_value();
* msg.set_field(StringRef(temp));
* msg.field = StringRef(temp);
* return this->send_message(msg); // temp is valid during encoding
*
* Unsafe Patterns (WILL cause crashes/corruption):
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
* 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
* 1. Temporaries: msg.field = StringRef(obj.get_string()) // get_string() returns by value
* 2. Concatenation: msg.field = StringRef(str1 + str2) // Result is temporary
*
* For unsafe patterns, store in a local variable first:
* std::string temp = get_string(); // or str1 + str2
* msg.set_field(StringRef(temp));
* msg.field = StringRef(temp);
*
* The send_*_response pattern ensures proper lifetime management by encoding
* within the same function scope where temporaries are created.
@@ -334,7 +334,7 @@ class ProtoWriteBuffer {
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
this->encode_uint64(field_id, encode_zigzag64(value), force);
}
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
void encode_message(uint32_t field_id, const ProtoMessage &value);
std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected:
@@ -795,7 +795,7 @@ class ProtoSize {
};
// Implementation of encode_message - must be after ProtoMessage is defined
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first
@@ -833,9 +833,6 @@ class ProtoService {
virtual bool is_authenticated() = 0;
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
#ifdef USE_API_PASSWORD
virtual void on_unauthenticated_access() = 0;
#endif
virtual void on_no_setup_connection() = 0;
/**
* Create a buffer with a reserved size.
@@ -873,20 +870,7 @@ class ProtoService {
return true;
}
inline bool check_authenticated_() {
#ifdef USE_API_PASSWORD
if (!this->check_connection_setup_()) {
return false;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return false;
}
return true;
#else
return this->check_connection_setup_();
#endif
}
inline bool check_authenticated_() { return this->check_connection_setup_(); }
};
} // namespace esphome::api

View File

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

View File

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

View File

@@ -46,7 +46,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.set_name(StringRef(this->name_));
msg.name = StringRef(this->name_);
msg.key = this->key_;
msg.supports_response = this->supports_response_;
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
@@ -54,7 +54,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.set_name(StringRef(this->arg_names_[i]));
arg.name = StringRef(this->arg_names_[i]);
}
return msg;
}
@@ -108,7 +108,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.set_name(StringRef(this->name_));
msg.name = StringRef(this->name_);
msg.key = this->key_;
msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
@@ -116,7 +116,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.set_name(StringRef(this->arg_names_[i]));
arg.name = StringRef(this->arg_names_[i]);
}
return msg;
}
@@ -255,7 +255,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
bool return_response = std::get<1>(args);
if (!return_response) {
// Client doesn't want response data, just send success/error
this->parent_->send_action_response(call_id, success, error_message);
this->parent_->send_action_response(call_id, success, StringRef(error_message));
return;
}
}
@@ -265,12 +265,12 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
json::JsonBuilder builder;
this->json_builder_(x..., builder.root());
std::string json_str = builder.serialize();
this->parent_->send_action_response(call_id, success, error_message,
this->parent_->send_action_response(call_id, success, StringRef(error_message),
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
return;
}
#endif
this->parent_->send_action_response(call_id, success, error_message);
this->parent_->send_action_response(call_id, success, StringRef(error_message));
}
protected:

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
#pragma once
#include <climits>
#include "abstract_aqi_calculator.h"
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
namespace esphome::aqi {
class AQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
}
protected:
static constexpr int NUM_LEVELS = 6;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
{255, 354}, {355, 424}, {425, INT_MAX}};
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1;
}
int aqi_lo = INDEX_GRID[grid_index][0];
int aqi_hi = INDEX_GRID[grid_index][1];
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;
}
}
return -1;
}
};
} // namespace esphome::aqi

View File

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

View File

@@ -0,0 +1,52 @@
#include "aqi_sensor.h"
#include "esphome/core/log.h"
namespace esphome::aqi {
static const char *const TAG = "aqi";
void AQISensor::setup() {
if (this->pm_2_5_sensor_ != nullptr) {
this->pm_2_5_sensor_->add_on_state_callback([this](float value) {
this->pm_2_5_value_ = value;
// Defer calculation to avoid double-publishing if both sensors update in the same loop
this->defer("update", [this]() { this->calculate_aqi_(); });
});
}
if (this->pm_10_0_sensor_ != nullptr) {
this->pm_10_0_sensor_->add_on_state_callback([this](float value) {
this->pm_10_0_value_ = value;
this->defer("update", [this]() { this->calculate_aqi_(); });
});
}
}
void AQISensor::dump_config() {
ESP_LOGCONFIG(TAG, "AQI Sensor:");
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
if (this->pm_2_5_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
}
if (this->pm_10_0_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str());
}
LOG_SENSOR(" ", "AQI", this);
}
void AQISensor::calculate_aqi_() {
if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) {
return;
}
AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
if (calculator == nullptr) {
ESP_LOGW(TAG, "Unknown AQI calculator type");
return;
}
uint16_t aqi =
calculator->get_aqi(static_cast<uint16_t>(this->pm_2_5_value_), static_cast<uint16_t>(this->pm_10_0_value_));
this->publish_state(aqi);
}
} // namespace esphome::aqi

View File

@@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "aqi_calculator_factory.h"
namespace esphome::aqi {
class AQISensor : public sensor::Sensor, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
protected:
void calculate_aqi_();
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
AQICalculatorType aqi_calc_type_{AQI_TYPE};
AQICalculatorFactory aqi_calculator_factory_;
float pm_2_5_value_{NAN};
float pm_10_0_value_{NAN};
};
} // namespace esphome::aqi

View File

@@ -0,0 +1,49 @@
#pragma once
#include "abstract_aqi_calculator.h"
namespace esphome::aqi {
class CAQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
}
protected:
static constexpr int NUM_LEVELS = 5;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1;
}
int aqi_lo = INDEX_GRID[grid_index][0];
int aqi_hi = INDEX_GRID[grid_index][1];
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;
}
}
return -1;
}
};
} // namespace esphome::aqi

View File

@@ -0,0 +1,51 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_PM_2_5,
CONF_PM_10_0,
DEVICE_CLASS_AQI,
STATE_CLASS_MEASUREMENT,
)
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
CODEOWNERS = ["@jasstrong"]
DEPENDENCIES = ["sensor"]
UNIT_INDEX = "index"
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = (
sensor.sensor_schema(
AQISensor,
unit_of_measurement=UNIT_INDEX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_AQI,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor),
cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor),
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
AQI_CALCULATION_TYPE, upper=True
),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5])
cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor))
pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0])
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))

View File

@@ -305,12 +305,14 @@ bool AS3935Component::calibrate_oscillator() {
}
void AS3935Component::tune_antenna() {
ESP_LOGI(TAG, "Starting antenna tuning");
uint8_t div_ratio = this->read_div_ratio();
uint8_t tune_val = this->read_capacitance();
ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio);
ESP_LOGI(TAG, "Internal Capacitor is set to: %d", tune_val);
ESP_LOGI(TAG, "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio");
ESP_LOGI(TAG,
"Starting antenna tuning\n"
"Division Ratio is set to: %d\n"
"Internal Capacitor is set to: %d\n"
"Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio",
div_ratio, tune_val);
this->display_oscillator(true, ANTFREQ);
}

View File

@@ -1,37 +1,50 @@
# Dummy integration to allow relying on AsyncTCP
# Async TCP client support for all platforms
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
cv.only_with_arduino,
cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
]
),
)
def AUTO_LOAD() -> list[str]:
# Socket component needed for platforms using socket-based implementation
# ESP32, ESP8266, RP2040, and LibreTiny use AsyncTCP libraries, others use sockets
if (
not CORE.is_esp32
and not CORE.is_esp8266
and not CORE.is_rp2040
and not CORE.is_libretiny
):
return ["socket"]
return []
# Support all platforms - Arduino/ESP-IDF get libraries, other platforms use socket implementation
CONFIG_SCHEMA = cv.Schema({})
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny:
if CORE.is_esp32:
# https://github.com/ESP32Async/AsyncTCP
from esphome.components.esp32 import add_idf_component
add_idf_component(name="esp32async/asynctcp", ref="3.4.91")
elif CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP
cg.add_library("ESP32Async/AsyncTCP", "3.4.5")
elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
elif CORE.is_rp2040:
# https://github.com/khoih-prog/AsyncTCP_RP2040W
cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0")
# Other platforms (host, etc) use socket-based implementation
def FILTER_SOURCE_FILES() -> list[str]:
# Exclude socket implementation for platforms that use AsyncTCP libraries
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny:
return ["async_tcp_socket.cpp"]
return []

View File

@@ -0,0 +1,16 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
// Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny
#include <AsyncTCP.h>
#elif defined(USE_ESP8266)
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
#include <ESPAsyncTCP.h>
#elif defined(USE_RP2040)
// Use AsyncTCP_RP2040W library for RP2040
#include <AsyncTCP_RP2040W.h>
#else
// Use socket-based implementation for other platforms
#include "async_tcp_socket.h"
#endif

View File

@@ -0,0 +1,162 @@
#include "async_tcp_socket.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/network/util.h"
#include "esphome/core/log.h"
#include <cerrno>
#include <sys/select.h>
namespace esphome::async_tcp {
static const char *const TAG = "async_tcp";
// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers).
// This implementation only runs on ESP-IDF and host which have ample stack.
static constexpr size_t READ_BUFFER_SIZE = 1460;
bool AsyncClient::connect(const char *host, uint16_t port) {
if (connected_ || connecting_) {
ESP_LOGW(TAG, "Already connected/connecting");
return false;
}
// Resolve address
struct sockaddr_storage addr;
socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port);
if (addrlen == 0) {
ESP_LOGE(TAG, "Invalid address: %s", host);
if (error_cb_)
error_cb_(error_arg_, this, -1);
return false;
}
// Create socket with loop monitoring
int family = ((struct sockaddr *) &addr)->sa_family;
socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP);
if (!socket_) {
ESP_LOGE(TAG, "Failed to create socket");
if (error_cb_)
error_cb_(error_arg_, this, -1);
return false;
}
socket_->setblocking(false);
int err = socket_->connect((struct sockaddr *) &addr, addrlen);
if (err == 0) {
// Connection succeeded immediately (rare, but possible for localhost)
connected_ = true;
if (connect_cb_)
connect_cb_(connect_arg_, this);
return true;
}
if (errno != EINPROGRESS) {
ESP_LOGE(TAG, "Connect failed: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
return false;
}
connecting_ = true;
return true;
}
void AsyncClient::close() {
socket_.reset();
bool was_connected = connected_;
connected_ = false;
connecting_ = false;
if (was_connected && disconnect_cb_)
disconnect_cb_(disconnect_arg_, this);
}
size_t AsyncClient::write(const char *data, size_t len) {
if (!socket_ || !connected_)
return 0;
ssize_t sent = socket_->write(data, len);
if (sent < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
ESP_LOGE(TAG, "Write error: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
}
return 0;
}
return sent;
}
void AsyncClient::loop() {
if (!socket_)
return;
if (connecting_) {
// For connecting, we need to check writability, not readability
// The Application's select() only monitors read FDs, so we do our own check here
// For ESP platforms lwip_select() might be faster, but this code isn't used
// on those platforms anyway. If it was, we'd fix the Application select()
// to report writability instead of doing it this way.
int fd = socket_->get_fd();
if (fd < 0) {
ESP_LOGW(TAG, "Invalid socket fd");
close();
return;
}
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);
struct timeval tv = {0, 0};
int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
if (ret > 0 && FD_ISSET(fd, &writefds)) {
int error = 0;
socklen_t len = sizeof(error);
if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
connecting_ = false;
connected_ = true;
if (connect_cb_)
connect_cb_(connect_arg_, this);
} else {
ESP_LOGW(TAG, "Connection failed: %d", error);
close();
if (error_cb_)
error_cb_(error_arg_, this, error);
}
} else if (ret < 0) {
ESP_LOGE(TAG, "Select error: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
}
} else if (connected_) {
// For connected sockets, use the Application's select() results
if (!socket_->ready())
return;
uint8_t buf[READ_BUFFER_SIZE];
ssize_t len = socket_->read(buf, READ_BUFFER_SIZE);
if (len == 0) {
ESP_LOGI(TAG, "Connection closed by peer");
close();
} else if (len > 0) {
if (data_cb_)
data_cb_(data_arg_, this, buf, len);
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
ESP_LOGW(TAG, "Read error: %d", errno);
close();
if (error_cb_)
error_cb_(error_arg_, this, errno);
}
}
}
} // namespace esphome::async_tcp
#endif

View File

@@ -0,0 +1,73 @@
#pragma once
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/socket/socket.h"
#include <functional>
#include <memory>
#include <string>
#include <utility>
namespace esphome::async_tcp {
/// AsyncClient API for platforms using sockets (ESP-IDF, host, etc.)
/// NOTE: This class is NOT thread-safe. All methods must be called from the main loop.
class AsyncClient {
public:
using AcConnectHandler = std::function<void(void *, AsyncClient *)>;
using AcDataHandler = std::function<void(void *, AsyncClient *, void *data, size_t len)>;
using AcErrorHandler = std::function<void(void *, AsyncClient *, int8_t error)>;
AsyncClient() = default;
~AsyncClient() = default;
[[nodiscard]] bool connect(const char *host, uint16_t port);
void close();
[[nodiscard]] bool connected() const { return connected_; }
size_t write(const char *data, size_t len);
void onConnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
connect_cb_ = std::move(cb);
connect_arg_ = arg;
}
void onDisconnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
disconnect_cb_ = std::move(cb);
disconnect_arg_ = arg;
}
/// Set data callback. NOTE: data pointer is only valid during callback execution.
void onData(AcDataHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
data_cb_ = std::move(cb);
data_arg_ = arg;
}
void onError(AcErrorHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
error_cb_ = std::move(cb);
error_arg_ = arg;
}
// Must be called from loop()
void loop();
private:
std::unique_ptr<esphome::socket::Socket> socket_;
AcConnectHandler connect_cb_{nullptr};
void *connect_arg_{nullptr};
AcConnectHandler disconnect_cb_{nullptr};
void *disconnect_arg_{nullptr};
AcDataHandler data_cb_{nullptr};
void *data_arg_{nullptr};
AcErrorHandler error_cb_{nullptr};
void *error_arg_{nullptr};
bool connected_{false};
bool connecting_{false};
};
} // namespace esphome::async_tcp
// Expose AsyncClient in global namespace to match library behavior
using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers)
#endif

View File

@@ -21,7 +21,9 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
const char *addr_str = device.address_str_to(addr_buf);
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str);
bool success = false;
for (auto &service_data : device.get_service_datas()) {
@@ -32,7 +34,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
if (!(parse_message_(service_data.data, *res))) {
continue;
}
if (!(report_results_(res, device.address_str()))) {
if (!(report_results_(res, addr_str))) {
continue;
}
if (res->temperature.has_value() && this->temperature_ != nullptr)
@@ -103,13 +105,13 @@ bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, Parse
return true;
}
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const std::string &address) {
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const char *address) {
if (!result.has_value()) {
ESP_LOGVV(TAG, "report_results(): no results available.");
return false;
}
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address);
if (result->temperature.has_value()) {
ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature);

View File

@@ -41,7 +41,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results_(const optional<ParseResult> &result, const std::string &address);
bool report_results_(const optional<ParseResult> &result, const char *address);
};
} // namespace atc_mithermometer

View File

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

View File

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

View File

@@ -22,7 +22,8 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf));
const auto &service_datas = device.get_service_datas();
if (service_datas.size() != 1) {
ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());

View File

@@ -193,8 +193,9 @@ bool BedJetHub::discover_characteristics_() {
result = false;
} else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
char uuid_buf[espbt::UUID_STR_LEN];
ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
descr->uuid.to_string().c_str());
descr->uuid.to_str(uuid_buf));
result = false;
} else {
this->config_descr_status_ = descr->handle;
@@ -216,11 +217,14 @@ bool BedJetHub::discover_characteristics_() {
}
}
ESP_LOGI(TAG, "[%s] Discovered service characteristics: ", this->get_name().c_str());
ESP_LOGI(TAG, " - Command char: 0x%x", this->char_handle_cmd_);
ESP_LOGI(TAG, " - Status char: 0x%x", this->char_handle_status_);
ESP_LOGI(TAG, " - config descriptor: 0x%x", this->config_descr_status_);
ESP_LOGI(TAG, " - Name char: 0x%x", this->char_handle_name_);
ESP_LOGI(TAG,
"[%s] Discovered service characteristics:\n"
" - Command char: 0x%x\n"
" - Status char: 0x%x\n"
" - config descriptor: 0x%x\n"
" - Name char: 0x%x",
this->get_name().c_str(), this->char_handle_cmd_, this->char_handle_status_, this->config_descr_status_,
this->char_handle_name_);
return result;
}

View File

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

View File

@@ -1,8 +1,8 @@
#include "bh1750.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace bh1750 {
namespace esphome::bh1750 {
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_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:
@@ -43,74 +68,7 @@ void BH1750Sensor::setup() {
this->mark_failed();
return;
}
}
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);
});
this->state_ = IDLE;
}
void BH1750Sensor::dump_config() {
@@ -124,45 +82,189 @@ void BH1750Sensor::dump_config() {
}
void BH1750Sensor::update() {
// first do a quick measurement in L-mode with full range
// to find right range
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
const uint32_t now = millis();
// Start coarse measurement to determine optimal mode/mtreg
if (this->state_ != IDLE) {
// Safety timeout: reset if stuck
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;
}
}
BH1750Mode use_mode;
uint8_t use_mtreg;
if (val <= 7000) {
use_mode = BH1750_MODE_H2;
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);
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
this->read_lx_(use_mode, use_mtreg, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
this->state_ = WAITING_COARSE_MEASUREMENT;
this->enable_loop(); // Enable loop while measurement in progress
}
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->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; }
} // namespace bh1750
} // namespace esphome
} // namespace esphome::bh1750

View File

@@ -4,10 +4,9 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bh1750 {
namespace esphome::bh1750 {
enum BH1750Mode {
enum BH1750Mode : uint8_t {
BH1750_MODE_L,
BH1750_MODE_H,
BH1750_MODE_H2,
@@ -21,13 +20,36 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
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};
// 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
} // namespace esphome::bh1750

View File

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

View File

@@ -3,7 +3,7 @@ from logging import getLogger
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
from esphome.components import mqtt, web_server, zigbee
from esphome.components.const import CONF_ON_STATE_CHANGE
import esphome.config_validation as cv
from esphome.const import (
@@ -439,6 +439,7 @@ def validate_publish_initial_state(value):
_BINARY_SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(zigbee.BINARY_SENSOR_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
@@ -520,6 +521,7 @@ _BINARY_SENSOR_SCHEMA = (
_BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor"))
_BINARY_SENSOR_SCHEMA.add_extra(zigbee.validate_binary_sensor)
def binary_sensor_schema(
@@ -621,6 +623,8 @@ async def setup_binary_sensor_core_(var, config):
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
await zigbee.setup_binary_sensor(var, config)
async def register_binary_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@@ -21,8 +21,10 @@ void MultiClickTrigger::on_state_(bool state) {
// Start matching
MultiClickTriggerEvent evt = this->timing_[0];
if (evt.state == state) {
ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length);
ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
ESP_LOGV(TAG,
"START min=%" PRIu32 " max=%" PRIu32 "\n"
"Multi Click: Starting multi click action!",
evt.min_length, evt.max_length);
this->at_index_ = 1;
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });

View File

@@ -7,8 +7,12 @@
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars)
static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64;
namespace esphome::ble_client {
// placeholder class for static TAG .
@@ -151,7 +155,10 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
#endif
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
@@ -179,8 +186,10 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
char char_buf[esp32_ble::UUID_STR_LEN];
char service_buf[esp32_ble::UUID_STR_LEN];
esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf));
break;
}
this->char_handle_ = chr->handle;
@@ -192,11 +201,13 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
char char_buf[esp32_ble::UUID_STR_LEN];
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf));
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
char char_buf[esp32_ble::UUID_STR_LEN];
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
ble_client_->address_str());
break;
}

View File

@@ -9,12 +9,15 @@ static const char *const TAG = "ble_binary_output";
void BLEBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
this->service_uuid_.to_str(service_buf);
this->char_uuid_.to_str(char_buf);
ESP_LOGCONFIG(TAG,
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s",
this->parent_->address_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
this->parent_->address_str(), service_buf, char_buf);
LOG_BINARY_OUTPUT(this);
}
@@ -24,8 +27,10 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(),
this->service_uuid_.to_string().c_str());
char char_buf[esp32_ble::UUID_STR_LEN];
char service_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_str(char_buf),
this->service_uuid_.to_str(service_buf));
break;
}
this->char_handle_ = chr->handle;
@@ -37,20 +42,24 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(),
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_str(char_buf),
this->require_response_ ? "" : "out");
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
this->parent()->address_str());
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.handle == this->char_handle_) {
if (param->write.status != 0)
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
if (param->write.status != 0) {
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_str(char_buf), param->write.status);
}
}
break;
}
@@ -60,18 +69,19 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
}
void BLEBinaryOutput::write_state(bool state) {
char char_buf[esp32_ble::UUID_STR_LEN];
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_string().c_str());
this->char_uuid_.to_str(char_buf));
return;
}
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_str(char_buf), state_as_uint);
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_,
sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_GATT_OK)
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err);
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err);
}
} // namespace esphome::ble_client

View File

@@ -18,14 +18,17 @@ void BLESensor::loop() {
void BLESensor::dump_config() {
LOG_SENSOR("", "BLE Sensor", this);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGCONFIG(TAG,
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s\n"
" Descriptor UUID : %s\n"
" Notifications : %s",
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
this->parent()->address_str(), this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}
@@ -51,8 +54,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (chr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf));
break;
}
this->handle = chr->handle;
@@ -61,9 +66,12 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (descr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
this->descr_uuid_.to_string().c_str());
this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf),
this->descr_uuid_.to_str(descr_buf));
break;
}
this->handle = descr->handle;
@@ -109,7 +117,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str());
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_str(char_buf));
}
break;
}

View File

@@ -21,7 +21,7 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
param->notify.handle != this->sensor_->handle)
break;
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
this->trigger(std::string(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len));
}
default:
break;

View File

@@ -11,8 +11,6 @@ namespace esphome::ble_client {
static const char *const TAG = "ble_text_sensor";
static const std::string EMPTY = "";
void BLETextSensor::loop() {
// Parent BLEClientNode has a loop() method, but this component uses
// polling via update() and BLE callbacks so loop isn't needed
@@ -21,14 +19,17 @@ void BLETextSensor::loop() {
void BLETextSensor::dump_config() {
LOG_TEXT_SENSOR("", "BLE Text Sensor", this);
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGCONFIG(TAG,
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s\n"
" Descriptor UUID : %s\n"
" Notifications : %s",
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
this->parent()->address_str(), this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}
@@ -44,7 +45,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
case ESP_GATTC_CLOSE_EVT: {
this->status_set_warning();
this->publish_state(EMPTY);
this->publish_state("");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
@@ -52,9 +53,11 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
this->status_set_warning();
this->publish_state(EMPTY);
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
this->publish_state("");
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
this->char_uuid_.to_str(char_buf));
break;
}
this->handle = chr->handle;
@@ -62,10 +65,13 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
auto *descr = chr->get_descriptor(this->descr_uuid_);
if (descr == nullptr) {
this->status_set_warning();
this->publish_state(EMPTY);
this->publish_state("");
char service_buf[esp32_ble::UUID_STR_LEN];
char char_buf[esp32_ble::UUID_STR_LEN];
char descr_buf[esp32_ble::UUID_STR_LEN];
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
this->descr_uuid_.to_string().c_str());
this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf),
this->descr_uuid_.to_str(descr_buf));
break;
}
this->handle = descr->handle;
@@ -91,7 +97,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
this->status_clear_warning();
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
this->publish_state(reinterpret_cast<const char *>(param->read.value), param->read.value_len);
}
break;
}
@@ -100,7 +106,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
param->notify.handle, param->notify.value[0]);
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
this->publish_state(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
@@ -113,11 +119,6 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
}
std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) {
std::string text(value, value + value_len);
return text;
}
void BLETextSensor::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
@@ -132,7 +133,7 @@ void BLETextSensor::update() {
ESP_GATT_AUTH_REQ_NONE);
if (status) {
this->status_set_warning();
this->publish_state(EMPTY);
this->publish_state("");
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
}
}

View File

@@ -29,7 +29,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_enable_notify(bool notify) { this->notify_ = notify; }
std::string parse_data(uint8_t *value, uint16_t value_len);
uint16_t handle;
protected:

View File

@@ -103,8 +103,10 @@ void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t
#endif
void BLENUS::dump_config() {
ESP_LOGCONFIG(TAG, "ble nus:");
ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_));
ESP_LOGCONFIG(TAG,
"ble nus:\n"
" log: %s",
YESNO(this->expose_log_));
uint32_t mtu = 0;
bt_conn *conn = this->conn_.load();
if (conn) {

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