esphome 2025.9.3__py3-none-any.whl → 2025.10.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. esphome/__main__.py +94 -31
  2. esphome/address_cache.py +142 -0
  3. esphome/automation.py +130 -32
  4. esphome/build_gen/platformio.py +1 -3
  5. esphome/codegen.py +1 -0
  6. esphome/components/animation/animation.cpp +2 -2
  7. esphome/components/api/__init__.py +166 -3
  8. esphome/components/api/api_connection.cpp +84 -41
  9. esphome/components/api/api_connection.h +22 -16
  10. esphome/components/api/api_frame_helper.cpp +33 -19
  11. esphome/components/api/api_frame_helper.h +19 -4
  12. esphome/components/api/api_frame_helper_noise.cpp +41 -53
  13. esphome/components/api/api_frame_helper_noise.h +1 -1
  14. esphome/components/api/api_frame_helper_plaintext.cpp +22 -31
  15. esphome/components/api/api_frame_helper_plaintext.h +1 -1
  16. esphome/components/api/api_pb2.cpp +189 -15
  17. esphome/components/api/api_pb2.h +132 -20
  18. esphome/components/api/api_pb2_dump.cpp +97 -9
  19. esphome/components/api/api_pb2_service.cpp +118 -160
  20. esphome/components/api/api_pb2_service.h +31 -3
  21. esphome/components/api/api_server.cpp +68 -10
  22. esphome/components/api/api_server.h +32 -4
  23. esphome/components/api/custom_api_device.h +8 -8
  24. esphome/components/api/homeassistant_service.h +123 -6
  25. esphome/components/api/proto.h +6 -2
  26. esphome/components/api/user_services.h +2 -2
  27. esphome/components/as7341/sensor.py +1 -1
  28. esphome/components/audio/__init__.py +1 -1
  29. esphome/components/audio/audio.cpp +1 -1
  30. esphome/components/audio/audio_decoder.cpp +9 -9
  31. esphome/components/bl0906/bl0906.cpp +2 -2
  32. esphome/components/bl0942/bl0942.cpp +2 -2
  33. esphome/components/ble_client/__init__.py +1 -1
  34. esphome/components/bluetooth_proxy/__init__.py +4 -30
  35. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +11 -4
  36. esphome/components/bluetooth_proxy/bluetooth_connection.h +2 -2
  37. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
  38. esphome/components/camera_encoder/__init__.py +2 -4
  39. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +4 -2
  40. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +3 -1
  41. esphome/components/canbus/canbus.cpp +7 -5
  42. esphome/components/canbus/canbus.h +7 -7
  43. esphome/components/captive_portal/__init__.py +18 -1
  44. esphome/components/captive_portal/captive_portal.cpp +40 -46
  45. esphome/components/captive_portal/captive_portal.h +20 -22
  46. esphome/components/captive_portal/dns_server_esp32_idf.cpp +205 -0
  47. esphome/components/captive_portal/dns_server_esp32_idf.h +27 -0
  48. esphome/components/ccs811/ccs811.cpp +1 -1
  49. esphome/components/climate/climate.cpp +10 -7
  50. esphome/components/cm1106/cm1106.cpp +1 -1
  51. esphome/components/copy/lock/copy_lock.cpp +1 -1
  52. esphome/components/cover/cover.cpp +1 -0
  53. esphome/components/daikin_arc/daikin_arc.cpp +19 -12
  54. esphome/components/dashboard_import/dashboard_import.cpp +1 -1
  55. esphome/components/dashboard_import/dashboard_import.h +1 -1
  56. esphome/components/deep_sleep/__init__.py +9 -2
  57. esphome/components/deep_sleep/deep_sleep_component.h +11 -9
  58. esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
  59. esphome/components/ektf2232/touchscreen/__init__.py +8 -5
  60. esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
  61. esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
  62. esphome/components/epaper_spi/__init__.py +1 -0
  63. esphome/components/epaper_spi/display.py +80 -0
  64. esphome/components/epaper_spi/epaper_spi.cpp +227 -0
  65. esphome/components/epaper_spi/epaper_spi.h +93 -0
  66. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
  67. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
  68. esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
  69. esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
  70. esphome/components/es7210/es7210.cpp +3 -3
  71. esphome/components/esp32/__init__.py +256 -340
  72. esphome/components/esp32/boards.py +81 -0
  73. esphome/components/esp32/preferences.cpp +23 -17
  74. esphome/components/esp32_ble/__init__.py +167 -44
  75. esphome/components/esp32_ble/ble.cpp +47 -3
  76. esphome/components/esp32_ble/ble.h +18 -0
  77. esphome/components/esp32_ble/ble_advertising.cpp +7 -3
  78. esphome/components/esp32_ble/ble_advertising.h +4 -0
  79. esphome/components/esp32_ble/ble_uuid.cpp +16 -42
  80. esphome/components/esp32_ble_beacon/__init__.py +3 -4
  81. esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +0 -4
  82. esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
  83. esphome/components/esp32_ble_server/__init__.py +28 -14
  84. esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
  85. esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
  86. esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
  87. esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
  88. esphome/components/esp32_ble_server/ble_server.cpp +59 -24
  89. esphome/components/esp32_ble_server/ble_server.h +38 -20
  90. esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
  91. esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
  92. esphome/components/esp32_ble_tracker/__init__.py +25 -80
  93. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -8
  94. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
  95. esphome/components/esp32_camera/__init__.py +1 -3
  96. esphome/components/esp32_can/esp32_can.cpp +22 -4
  97. esphome/components/esp32_can/esp32_can.h +3 -0
  98. esphome/components/esp32_hosted/__init__.py +2 -1
  99. esphome/components/esp32_improv/esp32_improv_component.cpp +135 -65
  100. esphome/components/esp32_improv/esp32_improv_component.h +7 -1
  101. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  102. esphome/components/esp8266/__init__.py +3 -3
  103. esphome/components/esphome/ota/__init__.py +21 -2
  104. esphome/components/esphome/ota/ota_esphome.cpp +456 -146
  105. esphome/components/esphome/ota/ota_esphome.h +49 -2
  106. esphome/components/ethernet/__init__.py +39 -22
  107. esphome/components/ethernet/ethernet_component.cpp +28 -5
  108. esphome/components/ethernet/ethernet_component.h +5 -1
  109. esphome/components/external_components/__init__.py +8 -6
  110. esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
  111. esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
  112. esphome/components/font/__init__.py +5 -5
  113. esphome/components/graph/graph.cpp +1 -1
  114. esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
  115. esphome/components/haier/hon_climate.cpp +2 -2
  116. esphome/components/haier/hon_climate.h +1 -1
  117. esphome/components/hdc1080/hdc1080.cpp +42 -34
  118. esphome/components/hdc1080/hdc1080.h +1 -3
  119. esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
  120. esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
  121. esphome/components/http_request/__init__.py +3 -3
  122. esphome/components/htu21d/htu21d.cpp +13 -18
  123. esphome/components/htu21d/htu21d.h +1 -1
  124. esphome/components/i2s_audio/__init__.py +1 -2
  125. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  126. esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
  127. esphome/components/improv_serial/improv_serial_component.cpp +12 -15
  128. esphome/components/improv_serial/improv_serial_component.h +6 -8
  129. esphome/components/json/json_util.cpp +42 -44
  130. esphome/components/json/json_util.h +57 -0
  131. esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
  132. esphome/components/key_collector/key_collector.h +4 -4
  133. esphome/components/libretiny/__init__.py +6 -6
  134. esphome/components/libretiny/preferences.cpp +23 -16
  135. esphome/components/light/light_call.cpp +98 -120
  136. esphome/components/light/light_call.h +17 -7
  137. esphome/components/lm75b/__init__.py +0 -0
  138. esphome/components/lm75b/lm75b.cpp +39 -0
  139. esphome/components/lm75b/lm75b.h +19 -0
  140. esphome/components/lm75b/sensor.py +34 -0
  141. esphome/components/lock/lock.h +12 -6
  142. esphome/components/logger/__init__.py +15 -27
  143. esphome/components/logger/logger.cpp +10 -20
  144. esphome/components/logger/logger.h +105 -62
  145. esphome/components/logger/logger_esp32.cpp +0 -48
  146. esphome/components/logger/logger_zephyr.cpp +2 -3
  147. esphome/components/logger/select/logger_level_select.cpp +6 -7
  148. esphome/components/logger/select/logger_level_select.h +7 -0
  149. esphome/components/ltr501/ltr501.cpp +7 -6
  150. esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
  151. esphome/components/matrix_keypad/matrix_keypad.h +4 -4
  152. esphome/components/max7219digit/max7219digit.cpp +1 -1
  153. esphome/components/mcp23xxx_base/mcp23xxx_base.h +3 -3
  154. esphome/components/mcp2515/mcp2515.cpp +31 -3
  155. esphome/components/mcp2515/mcp2515_defs.h +3 -1
  156. esphome/components/md5/md5.cpp +0 -26
  157. esphome/components/md5/md5.h +10 -20
  158. esphome/components/mdns/__init__.py +93 -19
  159. esphome/components/mdns/mdns_component.cpp +57 -94
  160. esphome/components/mdns/mdns_component.h +35 -11
  161. esphome/components/mdns/mdns_esp32.cpp +7 -13
  162. esphome/components/mdns/mdns_esp8266.cpp +7 -7
  163. esphome/components/mdns/mdns_libretiny.cpp +3 -4
  164. esphome/components/mdns/mdns_rp2040.cpp +3 -4
  165. esphome/components/mipi/__init__.py +1 -5
  166. esphome/components/mipi_spi/display.py +24 -8
  167. esphome/components/mipi_spi/mipi_spi.h +3 -3
  168. esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
  169. esphome/components/mmc5603/mmc5603.cpp +3 -3
  170. esphome/components/modbus/modbus.cpp +27 -13
  171. esphome/components/modbus/modbus.h +5 -3
  172. esphome/components/modbus/modbus_definitions.h +86 -0
  173. esphome/components/modbus_controller/__init__.py +29 -1
  174. esphome/components/modbus_controller/const.py +4 -0
  175. esphome/components/modbus_controller/modbus_controller.cpp +38 -13
  176. esphome/components/modbus_controller/modbus_controller.h +18 -29
  177. esphome/components/mpr121/mpr121.cpp +41 -42
  178. esphome/components/mpr121/mpr121.h +0 -1
  179. esphome/components/nau7802/nau7802.cpp +2 -2
  180. esphome/components/network/__init__.py +7 -3
  181. esphome/components/nextion/display.py +4 -4
  182. esphome/components/nextion/nextion.cpp +8 -8
  183. esphome/components/number/__init__.py +2 -0
  184. esphome/components/number/number_call.cpp +23 -12
  185. esphome/components/number/number_call.h +5 -0
  186. esphome/components/online_image/bmp_image.cpp +2 -1
  187. esphome/components/online_image/jpeg_image.cpp +4 -2
  188. esphome/components/opentherm/opentherm.cpp +5 -5
  189. esphome/components/opentherm/opentherm.h +3 -3
  190. esphome/components/openthread/openthread.cpp +11 -10
  191. esphome/components/openthread/openthread.h +0 -1
  192. esphome/components/ota/ota_backend.h +1 -0
  193. esphome/components/packages/__init__.py +10 -8
  194. esphome/components/packet_transport/packet_transport.cpp +2 -0
  195. esphome/components/pid/pid_controller.cpp +1 -1
  196. esphome/components/prometheus/prometheus_handler.cpp +239 -239
  197. esphome/components/psram/__init__.py +30 -28
  198. esphome/components/qmc5883l/qmc5883l.cpp +15 -0
  199. esphome/components/qmc5883l/qmc5883l.h +3 -0
  200. esphome/components/qmc5883l/sensor.py +31 -12
  201. esphome/components/remote_base/gobox_protocol.cpp +3 -3
  202. esphome/components/remote_receiver/__init__.py +14 -2
  203. esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
  204. esphome/components/remote_receiver/remote_receiver.h +4 -0
  205. esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
  206. esphome/components/remote_transmitter/__init__.py +2 -2
  207. esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
  208. esphome/components/rp2040/__init__.py +11 -11
  209. esphome/components/rtttl/rtttl.cpp +2 -2
  210. esphome/components/scd30/sensor.py +1 -1
  211. esphome/components/script/__init__.py +1 -1
  212. esphome/components/script/script.h +7 -7
  213. esphome/components/select/select.cpp +5 -4
  214. esphome/components/select/select_call.cpp +1 -1
  215. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  216. esphome/components/sensor/__init__.py +2 -0
  217. esphome/components/sha256/__init__.py +22 -0
  218. esphome/components/sha256/sha256.cpp +116 -0
  219. esphome/components/sha256/sha256.h +60 -0
  220. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  221. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  222. esphome/components/spi/__init__.py +0 -3
  223. esphome/components/split_buffer/__init__.py +5 -0
  224. esphome/components/split_buffer/split_buffer.cpp +133 -0
  225. esphome/components/split_buffer/split_buffer.h +40 -0
  226. esphome/components/sps30/sps30.cpp +14 -10
  227. esphome/components/sps30/sps30.h +2 -0
  228. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  229. esphome/components/st7789v/st7789v.cpp +3 -2
  230. esphome/components/statsd/statsd.cpp +1 -1
  231. esphome/components/substitutions/__init__.py +3 -1
  232. esphome/components/substitutions/jinja.py +13 -3
  233. esphome/components/sx126x/__init__.py +16 -0
  234. esphome/components/sx126x/sx126x.cpp +15 -1
  235. esphome/components/sx126x/sx126x.h +9 -1
  236. esphome/components/sx126x/sx126x_reg.h +2 -0
  237. esphome/components/text_sensor/text_sensor.cpp +16 -0
  238. esphome/components/text_sensor/text_sensor.h +3 -10
  239. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  240. esphome/components/tuya/select/tuya_select.cpp +1 -1
  241. esphome/components/tuya/tuya.cpp +29 -4
  242. esphome/components/uart/__init__.py +37 -27
  243. esphome/components/uart/uart.h +6 -0
  244. esphome/components/uart/uart_component.cpp +8 -0
  245. esphome/components/uart/uart_component.h +28 -0
  246. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  247. esphome/components/uart/uart_component_esp_idf.h +5 -2
  248. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  249. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  250. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  251. esphome/components/usb_host/__init__.py +12 -2
  252. esphome/components/usb_host/usb_host.h +89 -14
  253. esphome/components/usb_host/usb_host_client.cpp +157 -22
  254. esphome/components/usb_host/usb_host_component.cpp +1 -1
  255. esphome/components/usb_uart/__init__.py +0 -1
  256. esphome/components/usb_uart/ch34x.cpp +4 -4
  257. esphome/components/usb_uart/cp210x.cpp +3 -3
  258. esphome/components/usb_uart/usb_uart.cpp +88 -32
  259. esphome/components/usb_uart/usb_uart.h +30 -6
  260. esphome/components/valve/valve.cpp +1 -0
  261. esphome/components/veml7700/veml7700.cpp +7 -6
  262. esphome/components/version/version_text_sensor.cpp +2 -1
  263. esphome/components/voice_assistant/voice_assistant.cpp +3 -2
  264. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  265. esphome/components/web_server/list_entities.cpp +3 -4
  266. esphome/components/web_server/list_entities.h +8 -10
  267. esphome/components/web_server/ota/__init__.py +1 -1
  268. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  269. esphome/components/web_server/web_server.cpp +509 -404
  270. esphome/components/web_server/web_server.h +5 -6
  271. esphome/components/web_server/web_server_v1.cpp +21 -19
  272. esphome/components/web_server_base/__init__.py +5 -2
  273. esphome/components/web_server_base/web_server_base.h +27 -7
  274. esphome/components/web_server_idf/__init__.py +1 -1
  275. esphome/components/web_server_idf/multipart.cpp +2 -2
  276. esphome/components/web_server_idf/multipart.h +2 -2
  277. esphome/components/web_server_idf/utils.cpp +2 -2
  278. esphome/components/web_server_idf/utils.h +2 -2
  279. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  280. esphome/components/web_server_idf/web_server_idf.h +12 -10
  281. esphome/components/wifi/__init__.py +13 -11
  282. esphome/components/wifi/wifi_component.cpp +74 -56
  283. esphome/components/wifi/wifi_component.h +4 -4
  284. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  285. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  286. esphome/components/wireguard/__init__.py +1 -1
  287. esphome/components/wts01/__init__.py +0 -0
  288. esphome/components/wts01/sensor.py +41 -0
  289. esphome/components/wts01/wts01.cpp +91 -0
  290. esphome/components/wts01/wts01.h +27 -0
  291. esphome/components/zephyr/__init__.py +5 -5
  292. esphome/components/zwave_proxy/__init__.py +43 -0
  293. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  294. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  295. esphome/config.py +79 -24
  296. esphome/config_validation.py +13 -15
  297. esphome/const.py +9 -2
  298. esphome/core/__init__.py +33 -22
  299. esphome/core/component.cpp +28 -18
  300. esphome/core/component_iterator.h +2 -1
  301. esphome/core/config.py +15 -15
  302. esphome/core/defines.h +21 -0
  303. esphome/core/entity_helpers.py +9 -6
  304. esphome/core/hash_base.h +56 -0
  305. esphome/core/helpers.cpp +19 -3
  306. esphome/core/helpers.h +26 -0
  307. esphome/core/scheduler.cpp +5 -21
  308. esphome/core/scheduler.h +19 -8
  309. esphome/core/string_ref.h +1 -1
  310. esphome/core/time.cpp +5 -5
  311. esphome/cpp_generator.py +4 -29
  312. esphome/dashboard/const.py +21 -4
  313. esphome/dashboard/core.py +10 -8
  314. esphome/dashboard/dns.py +15 -0
  315. esphome/dashboard/entries.py +15 -21
  316. esphome/dashboard/models.py +76 -0
  317. esphome/dashboard/settings.py +7 -7
  318. esphome/dashboard/status/mdns.py +46 -2
  319. esphome/dashboard/web_server.py +367 -93
  320. esphome/espota2.py +112 -32
  321. esphome/external_files.py +6 -7
  322. esphome/git.py +8 -0
  323. esphome/helpers.py +124 -77
  324. esphome/loader.py +8 -9
  325. esphome/pins.py +2 -2
  326. esphome/platformio_api.py +56 -18
  327. esphome/storage_json.py +26 -21
  328. esphome/types.py +30 -2
  329. esphome/util.py +32 -16
  330. esphome/vscode.py +8 -8
  331. esphome/wizard.py +10 -10
  332. esphome/writer.py +50 -15
  333. esphome/yaml_util.py +37 -31
  334. esphome/zeroconf.py +12 -3
  335. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +12 -12
  336. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +340 -320
  337. esphome/components/event_emitter/__init__.py +0 -5
  338. esphome/components/event_emitter/event_emitter.cpp +0 -14
  339. esphome/components/event_emitter/event_emitter.h +0 -63
  340. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  341. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  342. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  343. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  344. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  345. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  346. esphome/core/string_ref.cpp +0 -12
  347. esphome/dashboard/util/file.py +0 -63
  348. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
  349. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
  350. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
  351. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/top_level.txt +0 -0
esphome/automation.py CHANGED
@@ -15,7 +15,10 @@ from esphome.const import (
15
15
  CONF_TYPE_ID,
16
16
  CONF_UPDATE_INTERVAL,
17
17
  )
18
+ from esphome.core import ID
19
+ from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
18
20
  from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
21
+ from esphome.types import ConfigType
19
22
  from esphome.util import Registry
20
23
 
21
24
 
@@ -49,11 +52,11 @@ def maybe_conf(conf, *validators):
49
52
  return validate
50
53
 
51
54
 
52
- def register_action(name, action_type, schema):
55
+ def register_action(name: str, action_type: MockObjClass, schema: cv.Schema):
53
56
  return ACTION_REGISTRY.register(name, action_type, schema)
54
57
 
55
58
 
56
- def register_condition(name, condition_type, schema):
59
+ def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
57
60
  return CONDITION_REGISTRY.register(name, condition_type, schema)
58
61
 
59
62
 
@@ -164,43 +167,78 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
164
167
 
165
168
 
166
169
  @register_condition("and", AndCondition, validate_condition_list)
167
- async def and_condition_to_code(config, condition_id, template_arg, args):
170
+ async def and_condition_to_code(
171
+ config: ConfigType,
172
+ condition_id: ID,
173
+ template_arg: cg.TemplateArguments,
174
+ args: TemplateArgsType,
175
+ ) -> MockObj:
168
176
  conditions = await build_condition_list(config, template_arg, args)
169
177
  return cg.new_Pvariable(condition_id, template_arg, conditions)
170
178
 
171
179
 
172
180
  @register_condition("or", OrCondition, validate_condition_list)
173
- async def or_condition_to_code(config, condition_id, template_arg, args):
181
+ async def or_condition_to_code(
182
+ config: ConfigType,
183
+ condition_id: ID,
184
+ template_arg: cg.TemplateArguments,
185
+ args: TemplateArgsType,
186
+ ) -> MockObj:
174
187
  conditions = await build_condition_list(config, template_arg, args)
175
188
  return cg.new_Pvariable(condition_id, template_arg, conditions)
176
189
 
177
190
 
178
191
  @register_condition("all", AndCondition, validate_condition_list)
179
- async def all_condition_to_code(config, condition_id, template_arg, args):
192
+ async def all_condition_to_code(
193
+ config: ConfigType,
194
+ condition_id: ID,
195
+ template_arg: cg.TemplateArguments,
196
+ args: TemplateArgsType,
197
+ ) -> MockObj:
180
198
  conditions = await build_condition_list(config, template_arg, args)
181
199
  return cg.new_Pvariable(condition_id, template_arg, conditions)
182
200
 
183
201
 
184
202
  @register_condition("any", OrCondition, validate_condition_list)
185
- async def any_condition_to_code(config, condition_id, template_arg, args):
203
+ async def any_condition_to_code(
204
+ config: ConfigType,
205
+ condition_id: ID,
206
+ template_arg: cg.TemplateArguments,
207
+ args: TemplateArgsType,
208
+ ) -> MockObj:
186
209
  conditions = await build_condition_list(config, template_arg, args)
187
210
  return cg.new_Pvariable(condition_id, template_arg, conditions)
188
211
 
189
212
 
190
213
  @register_condition("not", NotCondition, validate_potentially_and_condition)
191
- async def not_condition_to_code(config, condition_id, template_arg, args):
214
+ async def not_condition_to_code(
215
+ config: ConfigType,
216
+ condition_id: ID,
217
+ template_arg: cg.TemplateArguments,
218
+ args: TemplateArgsType,
219
+ ) -> MockObj:
192
220
  condition = await build_condition(config, template_arg, args)
193
221
  return cg.new_Pvariable(condition_id, template_arg, condition)
194
222
 
195
223
 
196
224
  @register_condition("xor", XorCondition, validate_condition_list)
197
- async def xor_condition_to_code(config, condition_id, template_arg, args):
225
+ async def xor_condition_to_code(
226
+ config: ConfigType,
227
+ condition_id: ID,
228
+ template_arg: cg.TemplateArguments,
229
+ args: TemplateArgsType,
230
+ ) -> MockObj:
198
231
  conditions = await build_condition_list(config, template_arg, args)
199
232
  return cg.new_Pvariable(condition_id, template_arg, conditions)
200
233
 
201
234
 
202
235
  @register_condition("lambda", LambdaCondition, cv.returning_lambda)
203
- async def lambda_condition_to_code(config, condition_id, template_arg, args):
236
+ async def lambda_condition_to_code(
237
+ config: ConfigType,
238
+ condition_id: ID,
239
+ template_arg: cg.TemplateArguments,
240
+ args: TemplateArgsType,
241
+ ) -> MockObj:
204
242
  lambda_ = await cg.process_lambda(config, args, return_type=bool)
205
243
  return cg.new_Pvariable(condition_id, template_arg, lambda_)
206
244
 
@@ -217,7 +255,12 @@ async def lambda_condition_to_code(config, condition_id, template_arg, args):
217
255
  }
218
256
  ).extend(cv.COMPONENT_SCHEMA),
219
257
  )
220
- async def for_condition_to_code(config, condition_id, template_arg, args):
258
+ async def for_condition_to_code(
259
+ config: ConfigType,
260
+ condition_id: ID,
261
+ template_arg: cg.TemplateArguments,
262
+ args: TemplateArgsType,
263
+ ) -> MockObj:
221
264
  condition = await build_condition(
222
265
  config[CONF_CONDITION], cg.TemplateArguments(), []
223
266
  )
@@ -231,7 +274,12 @@ async def for_condition_to_code(config, condition_id, template_arg, args):
231
274
  @register_action(
232
275
  "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
233
276
  )
234
- async def delay_action_to_code(config, action_id, template_arg, args):
277
+ async def delay_action_to_code(
278
+ config: ConfigType,
279
+ action_id: ID,
280
+ template_arg: cg.TemplateArguments,
281
+ args: TemplateArgsType,
282
+ ) -> MockObj:
235
283
  var = cg.new_Pvariable(action_id, template_arg)
236
284
  await cg.register_component(var, {})
237
285
  template_ = await cg.templatable(config, args, cg.uint32)
@@ -256,10 +304,15 @@ async def delay_action_to_code(config, action_id, template_arg, args):
256
304
  cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
257
305
  ),
258
306
  )
259
- async def if_action_to_code(config, action_id, template_arg, args):
307
+ async def if_action_to_code(
308
+ config: ConfigType,
309
+ action_id: ID,
310
+ template_arg: cg.TemplateArguments,
311
+ args: TemplateArgsType,
312
+ ) -> MockObj:
260
313
  cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
261
- conditions = await build_condition(config[cond_conf], template_arg, args)
262
- var = cg.new_Pvariable(action_id, template_arg, conditions)
314
+ condition = await build_condition(config[cond_conf], template_arg, args)
315
+ var = cg.new_Pvariable(action_id, template_arg, condition)
263
316
  if CONF_THEN in config:
264
317
  actions = await build_action_list(config[CONF_THEN], template_arg, args)
265
318
  cg.add(var.add_then(actions))
@@ -279,9 +332,14 @@ async def if_action_to_code(config, action_id, template_arg, args):
279
332
  }
280
333
  ),
281
334
  )
282
- async def while_action_to_code(config, action_id, template_arg, args):
283
- conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
284
- var = cg.new_Pvariable(action_id, template_arg, conditions)
335
+ async def while_action_to_code(
336
+ config: ConfigType,
337
+ action_id: ID,
338
+ template_arg: cg.TemplateArguments,
339
+ args: TemplateArgsType,
340
+ ) -> MockObj:
341
+ condition = await build_condition(config[CONF_CONDITION], template_arg, args)
342
+ var = cg.new_Pvariable(action_id, template_arg, condition)
285
343
  actions = await build_action_list(config[CONF_THEN], template_arg, args)
286
344
  cg.add(var.add_then(actions))
287
345
  return var
@@ -297,7 +355,12 @@ async def while_action_to_code(config, action_id, template_arg, args):
297
355
  }
298
356
  ),
299
357
  )
300
- async def repeat_action_to_code(config, action_id, template_arg, args):
358
+ async def repeat_action_to_code(
359
+ config: ConfigType,
360
+ action_id: ID,
361
+ template_arg: cg.TemplateArguments,
362
+ args: TemplateArgsType,
363
+ ) -> MockObj:
301
364
  var = cg.new_Pvariable(action_id, template_arg)
302
365
  count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
303
366
  cg.add(var.set_count(count_template))
@@ -320,9 +383,14 @@ _validate_wait_until = cv.maybe_simple_value(
320
383
 
321
384
 
322
385
  @register_action("wait_until", WaitUntilAction, _validate_wait_until)
323
- async def wait_until_action_to_code(config, action_id, template_arg, args):
324
- conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
325
- var = cg.new_Pvariable(action_id, template_arg, conditions)
386
+ async def wait_until_action_to_code(
387
+ config: ConfigType,
388
+ action_id: ID,
389
+ template_arg: cg.TemplateArguments,
390
+ args: TemplateArgsType,
391
+ ) -> MockObj:
392
+ condition = await build_condition(config[CONF_CONDITION], template_arg, args)
393
+ var = cg.new_Pvariable(action_id, template_arg, condition)
326
394
  if CONF_TIMEOUT in config:
327
395
  template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
328
396
  cg.add(var.set_timeout_value(template_))
@@ -331,7 +399,12 @@ async def wait_until_action_to_code(config, action_id, template_arg, args):
331
399
 
332
400
 
333
401
  @register_action("lambda", LambdaAction, cv.lambda_)
334
- async def lambda_action_to_code(config, action_id, template_arg, args):
402
+ async def lambda_action_to_code(
403
+ config: ConfigType,
404
+ action_id: ID,
405
+ template_arg: cg.TemplateArguments,
406
+ args: TemplateArgsType,
407
+ ) -> MockObj:
335
408
  lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
336
409
  return cg.new_Pvariable(action_id, template_arg, lambda_)
337
410
 
@@ -345,7 +418,12 @@ async def lambda_action_to_code(config, action_id, template_arg, args):
345
418
  }
346
419
  ),
347
420
  )
348
- async def component_update_action_to_code(config, action_id, template_arg, args):
421
+ async def component_update_action_to_code(
422
+ config: ConfigType,
423
+ action_id: ID,
424
+ template_arg: cg.TemplateArguments,
425
+ args: TemplateArgsType,
426
+ ) -> MockObj:
349
427
  comp = await cg.get_variable(config[CONF_ID])
350
428
  return cg.new_Pvariable(action_id, template_arg, comp)
351
429
 
@@ -359,7 +437,12 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
359
437
  }
360
438
  ),
361
439
  )
362
- async def component_suspend_action_to_code(config, action_id, template_arg, args):
440
+ async def component_suspend_action_to_code(
441
+ config: ConfigType,
442
+ action_id: ID,
443
+ template_arg: cg.TemplateArguments,
444
+ args: TemplateArgsType,
445
+ ) -> MockObj:
363
446
  comp = await cg.get_variable(config[CONF_ID])
364
447
  return cg.new_Pvariable(action_id, template_arg, comp)
365
448
 
@@ -376,7 +459,12 @@ async def component_suspend_action_to_code(config, action_id, template_arg, args
376
459
  }
377
460
  ),
378
461
  )
379
- async def component_resume_action_to_code(config, action_id, template_arg, args):
462
+ async def component_resume_action_to_code(
463
+ config: ConfigType,
464
+ action_id: ID,
465
+ template_arg: cg.TemplateArguments,
466
+ args: TemplateArgsType,
467
+ ) -> MockObj:
380
468
  comp = await cg.get_variable(config[CONF_ID])
381
469
  var = cg.new_Pvariable(action_id, template_arg, comp)
382
470
  if CONF_UPDATE_INTERVAL in config:
@@ -385,7 +473,9 @@ async def component_resume_action_to_code(config, action_id, template_arg, args)
385
473
  return var
386
474
 
387
475
 
388
- async def build_action(full_config, template_arg, args):
476
+ async def build_action(
477
+ full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
478
+ ) -> MockObj:
389
479
  registry_entry, config = cg.extract_registry_entry_config(
390
480
  ACTION_REGISTRY, full_config
391
481
  )
@@ -394,15 +484,19 @@ async def build_action(full_config, template_arg, args):
394
484
  return await builder(config, action_id, template_arg, args)
395
485
 
396
486
 
397
- async def build_action_list(config, templ, arg_type):
398
- actions = []
487
+ async def build_action_list(
488
+ config: list[ConfigType], templ: cg.TemplateArguments, arg_type: TemplateArgsType
489
+ ) -> list[MockObj]:
490
+ actions: list[MockObj] = []
399
491
  for conf in config:
400
492
  action = await build_action(conf, templ, arg_type)
401
493
  actions.append(action)
402
494
  return actions
403
495
 
404
496
 
405
- async def build_condition(full_config, template_arg, args):
497
+ async def build_condition(
498
+ full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
499
+ ) -> MockObj:
406
500
  registry_entry, config = cg.extract_registry_entry_config(
407
501
  CONDITION_REGISTRY, full_config
408
502
  )
@@ -411,15 +505,19 @@ async def build_condition(full_config, template_arg, args):
411
505
  return await builder(config, action_id, template_arg, args)
412
506
 
413
507
 
414
- async def build_condition_list(config, templ, args):
415
- conditions = []
508
+ async def build_condition_list(
509
+ config: ConfigType, templ: cg.TemplateArguments, args: TemplateArgsType
510
+ ) -> list[MockObj]:
511
+ conditions: list[MockObj] = []
416
512
  for conf in config:
417
513
  condition = await build_condition(conf, templ, args)
418
514
  conditions.append(condition)
419
515
  return conditions
420
516
 
421
517
 
422
- async def build_automation(trigger, args, config):
518
+ async def build_automation(
519
+ trigger: MockObj, args: TemplateArgsType, config: ConfigType
520
+ ) -> MockObj:
423
521
  arg_types = [arg[0] for arg in args]
424
522
  templ = cg.TemplateArguments(*arg_types)
425
523
  obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
@@ -1,5 +1,3 @@
1
- import os
2
-
3
1
  from esphome.const import __version__
4
2
  from esphome.core import CORE
5
3
  from esphome.helpers import mkdir_p, read_file, write_file_if_changed
@@ -63,7 +61,7 @@ def write_ini(content):
63
61
  update_storage_json()
64
62
  path = CORE.relative_build_path("platformio.ini")
65
63
 
66
- if os.path.isfile(path):
64
+ if path.is_file():
67
65
  text = read_file(path)
68
66
  content_format = find_begin_end(
69
67
  text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END
esphome/codegen.py CHANGED
@@ -12,6 +12,7 @@ from esphome.cpp_generator import ( # noqa: F401
12
12
  ArrayInitializer,
13
13
  Expression,
14
14
  LineComment,
15
+ LogStringLiteral,
15
16
  MockObj,
16
17
  MockObjClass,
17
18
  Pvariable,
@@ -26,12 +26,12 @@ uint32_t Animation::get_animation_frame_count() const { return this->animation_f
26
26
  int Animation::get_current_frame() const { return this->current_frame_; }
27
27
  void Animation::next_frame() {
28
28
  this->current_frame_++;
29
- if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
29
+ if (loop_count_ && static_cast<uint32_t>(this->current_frame_) == loop_end_frame_ &&
30
30
  (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
31
31
  this->current_frame_ = loop_start_frame_;
32
32
  this->loop_current_iteration_++;
33
33
  }
34
- if (this->current_frame_ >= animation_frame_count_) {
34
+ if (static_cast<uint32_t>(this->current_frame_) >= animation_frame_count_) {
35
35
  this->loop_current_iteration_ = 1;
36
36
  this->current_frame_ = 0;
37
37
  }
@@ -1,4 +1,5 @@
1
1
  import base64
2
+ import logging
2
3
 
3
4
  from esphome import automation
4
5
  from esphome.automation import Condition
@@ -8,34 +9,59 @@ import esphome.config_validation as cv
8
9
  from esphome.const import (
9
10
  CONF_ACTION,
10
11
  CONF_ACTIONS,
12
+ CONF_CAPTURE_RESPONSE,
11
13
  CONF_DATA,
12
14
  CONF_DATA_TEMPLATE,
13
15
  CONF_EVENT,
14
16
  CONF_ID,
15
17
  CONF_KEY,
18
+ CONF_MAX_CONNECTIONS,
16
19
  CONF_ON_CLIENT_CONNECTED,
17
20
  CONF_ON_CLIENT_DISCONNECTED,
21
+ CONF_ON_ERROR,
22
+ CONF_ON_SUCCESS,
18
23
  CONF_PASSWORD,
19
24
  CONF_PORT,
20
25
  CONF_REBOOT_TIMEOUT,
26
+ CONF_RESPONSE_TEMPLATE,
21
27
  CONF_SERVICE,
22
28
  CONF_SERVICES,
23
29
  CONF_TAG,
24
30
  CONF_TRIGGER_ID,
25
31
  CONF_VARIABLES,
26
32
  )
27
- from esphome.core import CORE, CoroPriority, coroutine_with_priority
33
+ from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
34
+ from esphome.cpp_generator import TemplateArgsType
35
+ from esphome.types import ConfigType
36
+
37
+ _LOGGER = logging.getLogger(__name__)
28
38
 
29
39
  DOMAIN = "api"
30
40
  DEPENDENCIES = ["network"]
31
- AUTO_LOAD = ["socket"]
32
41
  CODEOWNERS = ["@esphome/core"]
33
42
 
43
+
44
+ def AUTO_LOAD(config: ConfigType) -> list[str]:
45
+ """Conditionally auto-load json only when capture_response is used."""
46
+ base = ["socket"]
47
+
48
+ # Check if any homeassistant.action/homeassistant.service has capture_response: true
49
+ # This flag is set during config validation in _validate_response_config
50
+ if not config or CORE.data.get(DOMAIN, {}).get(CONF_CAPTURE_RESPONSE, False):
51
+ return base + ["json"]
52
+
53
+ return base
54
+
55
+
34
56
  api_ns = cg.esphome_ns.namespace("api")
35
57
  APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
36
58
  HomeAssistantServiceCallAction = api_ns.class_(
37
59
  "HomeAssistantServiceCallAction", automation.Action
38
60
  )
61
+ ActionResponse = api_ns.class_("ActionResponse")
62
+ HomeAssistantActionResponseTrigger = api_ns.class_(
63
+ "HomeAssistantActionResponseTrigger", automation.Trigger
64
+ )
39
65
  APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
40
66
 
41
67
  UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
@@ -55,6 +81,8 @@ CONF_BATCH_DELAY = "batch_delay"
55
81
  CONF_CUSTOM_SERVICES = "custom_services"
56
82
  CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
57
83
  CONF_HOMEASSISTANT_STATES = "homeassistant_states"
84
+ CONF_LISTEN_BACKLOG = "listen_backlog"
85
+ CONF_MAX_SEND_QUEUE = "max_send_queue"
58
86
 
59
87
 
60
88
  def validate_encryption_key(value):
@@ -101,6 +129,32 @@ def _encryption_schema(config):
101
129
  return ENCRYPTION_SCHEMA(config)
102
130
 
103
131
 
132
+ def _validate_api_config(config: ConfigType) -> ConfigType:
133
+ """Validate API configuration with mutual exclusivity check and deprecation warning."""
134
+ # Check if both password and encryption are configured
135
+ has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
136
+ has_encryption = CONF_ENCRYPTION in config
137
+
138
+ if has_password and has_encryption:
139
+ raise cv.Invalid(
140
+ "The 'password' and 'encryption' options are mutually exclusive. "
141
+ "The API client only supports one authentication method at a time. "
142
+ "Please remove one of them. "
143
+ "Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
144
+ "We strongly recommend using 'encryption' instead for better security."
145
+ )
146
+
147
+ # Warn about password deprecation
148
+ if has_password:
149
+ _LOGGER.warning(
150
+ "API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
151
+ "Please migrate to the 'encryption' configuration. "
152
+ "See https://esphome.io/components/api.html#configuration-variables"
153
+ )
154
+
155
+ return config
156
+
157
+
104
158
  CONFIG_SCHEMA = cv.All(
105
159
  cv.Schema(
106
160
  {
@@ -128,9 +182,46 @@ CONFIG_SCHEMA = cv.All(
128
182
  cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
129
183
  single=True
130
184
  ),
185
+ # Connection limits to prevent memory exhaustion on resource-constrained devices
186
+ # Each connection uses ~500-1000 bytes of RAM plus system resources
187
+ # Platform defaults based on available RAM and network stack implementation:
188
+ cv.SplitDefault(
189
+ CONF_LISTEN_BACKLOG,
190
+ esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
191
+ esp32=4, # More RAM (520KB), BSD sockets
192
+ rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
193
+ bk72xx=4, # Moderate RAM, BSD-style sockets
194
+ rtl87xx=4, # Moderate RAM, BSD-style sockets
195
+ host=4, # Abundant resources
196
+ ln882x=4, # Moderate RAM
197
+ ): cv.int_range(min=1, max=10),
198
+ cv.SplitDefault(
199
+ CONF_MAX_CONNECTIONS,
200
+ esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
201
+ esp32=8, # 520KB RAM available
202
+ rp2040=4, # 264KB RAM but LWIP constraints
203
+ bk72xx=8, # Moderate RAM
204
+ rtl87xx=8, # Moderate RAM
205
+ host=8, # Abundant resources
206
+ ln882x=8, # Moderate RAM
207
+ ): cv.int_range(min=1, max=20),
208
+ # Maximum queued send buffers per connection before dropping connection
209
+ # Each buffer uses ~8-12 bytes overhead plus actual message size
210
+ # Platform defaults based on available RAM and typical message rates:
211
+ cv.SplitDefault(
212
+ CONF_MAX_SEND_QUEUE,
213
+ esp8266=5, # Limited RAM, need to fail fast
214
+ esp32=8, # More RAM, can buffer more
215
+ rp2040=5, # Limited RAM
216
+ bk72xx=8, # Moderate RAM
217
+ rtl87xx=8, # Moderate RAM
218
+ host=16, # Abundant resources
219
+ ln882x=8, # Moderate RAM
220
+ ): cv.int_range(min=1, max=64),
131
221
  }
132
222
  ).extend(cv.COMPONENT_SCHEMA),
133
223
  cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
224
+ _validate_api_config,
134
225
  )
135
226
 
136
227
 
@@ -145,6 +236,11 @@ async def to_code(config):
145
236
  cg.add(var.set_password(config[CONF_PASSWORD]))
146
237
  cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
147
238
  cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
239
+ if CONF_LISTEN_BACKLOG in config:
240
+ cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
241
+ if CONF_MAX_CONNECTIONS in config:
242
+ cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
243
+ cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
148
244
 
149
245
  # Set USE_API_SERVICES if any services are enabled
150
246
  if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
@@ -213,6 +309,29 @@ async def to_code(config):
213
309
  KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
214
310
 
215
311
 
312
+ def _validate_response_config(config: ConfigType) -> ConfigType:
313
+ # Validate dependencies:
314
+ # - response_template requires capture_response: true
315
+ # - capture_response: true requires on_success
316
+ if CONF_RESPONSE_TEMPLATE in config and not config[CONF_CAPTURE_RESPONSE]:
317
+ raise cv.Invalid(
318
+ f"`{CONF_RESPONSE_TEMPLATE}` requires `{CONF_CAPTURE_RESPONSE}: true` to be set.",
319
+ path=[CONF_RESPONSE_TEMPLATE],
320
+ )
321
+
322
+ if config[CONF_CAPTURE_RESPONSE] and CONF_ON_SUCCESS not in config:
323
+ raise cv.Invalid(
324
+ f"`{CONF_CAPTURE_RESPONSE}: true` requires `{CONF_ON_SUCCESS}` to be set.",
325
+ path=[CONF_CAPTURE_RESPONSE],
326
+ )
327
+
328
+ # Track if any action uses capture_response for AUTO_LOAD
329
+ if config[CONF_CAPTURE_RESPONSE]:
330
+ CORE.data.setdefault(DOMAIN, {})[CONF_CAPTURE_RESPONSE] = True
331
+
332
+ return config
333
+
334
+
216
335
  HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
217
336
  cv.Schema(
218
337
  {
@@ -228,10 +347,15 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
228
347
  cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
229
348
  {cv.string: cv.returning_lambda}
230
349
  ),
350
+ cv.Optional(CONF_RESPONSE_TEMPLATE): cv.templatable(cv.string),
351
+ cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
352
+ cv.Optional(CONF_ON_SUCCESS): automation.validate_automation(single=True),
353
+ cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
231
354
  }
232
355
  ),
233
356
  cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
234
357
  cv.rename_key(CONF_SERVICE, CONF_ACTION),
358
+ _validate_response_config,
235
359
  )
236
360
 
237
361
 
@@ -245,7 +369,12 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
245
369
  HomeAssistantServiceCallAction,
246
370
  HOMEASSISTANT_ACTION_ACTION_SCHEMA,
247
371
  )
248
- async def homeassistant_service_to_code(config, action_id, template_arg, args):
372
+ async def homeassistant_service_to_code(
373
+ config: ConfigType,
374
+ action_id: ID,
375
+ template_arg: cg.TemplateArguments,
376
+ args: TemplateArgsType,
377
+ ):
249
378
  cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
250
379
  serv = await cg.get_variable(config[CONF_ID])
251
380
  var = cg.new_Pvariable(action_id, template_arg, serv, False)
@@ -260,6 +389,40 @@ async def homeassistant_service_to_code(config, action_id, template_arg, args):
260
389
  for key, value in config[CONF_VARIABLES].items():
261
390
  templ = await cg.templatable(value, args, None)
262
391
  cg.add(var.add_variable(key, templ))
392
+
393
+ if on_error := config.get(CONF_ON_ERROR):
394
+ cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
395
+ cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS")
396
+ cg.add(var.set_wants_status())
397
+ await automation.build_automation(
398
+ var.get_error_trigger(),
399
+ [(cg.std_string, "error"), *args],
400
+ on_error,
401
+ )
402
+
403
+ if on_success := config.get(CONF_ON_SUCCESS):
404
+ cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
405
+ cg.add(var.set_wants_status())
406
+ if config[CONF_CAPTURE_RESPONSE]:
407
+ cg.add(var.set_wants_response())
408
+ cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON")
409
+ await automation.build_automation(
410
+ var.get_success_trigger_with_response(),
411
+ [(cg.JsonObjectConst, "response"), *args],
412
+ on_success,
413
+ )
414
+
415
+ if response_template := config.get(CONF_RESPONSE_TEMPLATE):
416
+ templ = await cg.templatable(response_template, args, cg.std_string)
417
+ cg.add(var.set_response_template(templ))
418
+
419
+ else:
420
+ await automation.build_automation(
421
+ var.get_success_trigger(),
422
+ args,
423
+ on_success,
424
+ )
425
+
263
426
  return var
264
427
 
265
428