esphome 2025.9.2__py3-none-any.whl → 2025.10.0b1__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 (344) hide show
  1. esphome/__main__.py +87 -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 +167 -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 +78 -11
  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 +4 -4
  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/deep_sleep/__init__.py +9 -2
  55. esphome/components/deep_sleep/deep_sleep_component.h +11 -9
  56. esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
  57. esphome/components/ektf2232/touchscreen/__init__.py +8 -5
  58. esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
  59. esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
  60. esphome/components/epaper_spi/__init__.py +1 -0
  61. esphome/components/epaper_spi/display.py +80 -0
  62. esphome/components/epaper_spi/epaper_spi.cpp +227 -0
  63. esphome/components/epaper_spi/epaper_spi.h +93 -0
  64. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
  65. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
  66. esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
  67. esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
  68. esphome/components/es7210/es7210.cpp +3 -3
  69. esphome/components/esp32/__init__.py +254 -339
  70. esphome/components/esp32/boards.py +81 -0
  71. esphome/components/esp32/preferences.cpp +23 -17
  72. esphome/components/esp32_ble/__init__.py +159 -44
  73. esphome/components/esp32_ble/ble.cpp +47 -3
  74. esphome/components/esp32_ble/ble.h +18 -0
  75. esphome/components/esp32_ble/ble_advertising.cpp +7 -3
  76. esphome/components/esp32_ble/ble_advertising.h +4 -0
  77. esphome/components/esp32_ble/ble_uuid.cpp +16 -42
  78. esphome/components/esp32_ble_beacon/__init__.py +3 -4
  79. esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
  80. esphome/components/esp32_ble_server/__init__.py +28 -14
  81. esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
  82. esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
  83. esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
  84. esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
  85. esphome/components/esp32_ble_server/ble_server.cpp +59 -24
  86. esphome/components/esp32_ble_server/ble_server.h +38 -20
  87. esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
  88. esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
  89. esphome/components/esp32_ble_tracker/__init__.py +25 -80
  90. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -4
  91. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
  92. esphome/components/esp32_camera/__init__.py +1 -3
  93. esphome/components/esp32_can/esp32_can.cpp +22 -4
  94. esphome/components/esp32_can/esp32_can.h +3 -0
  95. esphome/components/esp32_hosted/__init__.py +2 -1
  96. esphome/components/esp32_improv/esp32_improv_component.cpp +102 -44
  97. esphome/components/esp32_improv/esp32_improv_component.h +6 -1
  98. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  99. esphome/components/esp8266/__init__.py +3 -3
  100. esphome/components/esphome/ota/__init__.py +21 -2
  101. esphome/components/esphome/ota/ota_esphome.cpp +455 -145
  102. esphome/components/esphome/ota/ota_esphome.h +49 -2
  103. esphome/components/ethernet/__init__.py +39 -22
  104. esphome/components/ethernet/ethernet_component.cpp +28 -5
  105. esphome/components/ethernet/ethernet_component.h +5 -1
  106. esphome/components/external_components/__init__.py +8 -6
  107. esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
  108. esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
  109. esphome/components/font/__init__.py +5 -5
  110. esphome/components/graph/graph.cpp +1 -1
  111. esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
  112. esphome/components/haier/hon_climate.cpp +2 -2
  113. esphome/components/haier/hon_climate.h +1 -1
  114. esphome/components/hdc1080/hdc1080.cpp +42 -34
  115. esphome/components/hdc1080/hdc1080.h +1 -3
  116. esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
  117. esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
  118. esphome/components/http_request/__init__.py +3 -3
  119. esphome/components/htu21d/htu21d.cpp +13 -18
  120. esphome/components/htu21d/htu21d.h +1 -1
  121. esphome/components/i2s_audio/__init__.py +1 -2
  122. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  123. esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
  124. esphome/components/improv_serial/improv_serial_component.cpp +12 -15
  125. esphome/components/improv_serial/improv_serial_component.h +6 -8
  126. esphome/components/json/json_util.cpp +35 -43
  127. esphome/components/json/json_util.h +57 -0
  128. esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
  129. esphome/components/key_collector/key_collector.h +4 -4
  130. esphome/components/libretiny/__init__.py +6 -6
  131. esphome/components/libretiny/preferences.cpp +23 -16
  132. esphome/components/light/light_call.cpp +98 -120
  133. esphome/components/light/light_call.h +17 -7
  134. esphome/components/lm75b/__init__.py +0 -0
  135. esphome/components/lm75b/lm75b.cpp +39 -0
  136. esphome/components/lm75b/lm75b.h +19 -0
  137. esphome/components/lm75b/sensor.py +34 -0
  138. esphome/components/lock/lock.h +12 -6
  139. esphome/components/logger/__init__.py +15 -27
  140. esphome/components/logger/logger.cpp +10 -20
  141. esphome/components/logger/logger.h +105 -62
  142. esphome/components/logger/logger_esp32.cpp +0 -48
  143. esphome/components/logger/logger_zephyr.cpp +2 -3
  144. esphome/components/logger/select/logger_level_select.cpp +6 -7
  145. esphome/components/logger/select/logger_level_select.h +7 -0
  146. esphome/components/ltr501/ltr501.cpp +7 -6
  147. esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
  148. esphome/components/matrix_keypad/matrix_keypad.h +4 -4
  149. esphome/components/max7219digit/max7219digit.cpp +1 -1
  150. esphome/components/mcp2515/mcp2515.cpp +31 -3
  151. esphome/components/mcp2515/mcp2515_defs.h +3 -1
  152. esphome/components/md5/md5.cpp +0 -26
  153. esphome/components/md5/md5.h +10 -20
  154. esphome/components/mdns/__init__.py +19 -6
  155. esphome/components/mdns/mdns_component.cpp +27 -59
  156. esphome/components/mdns/mdns_component.h +23 -10
  157. esphome/components/mdns/mdns_esp32.cpp +7 -7
  158. esphome/components/mdns/mdns_esp8266.cpp +6 -6
  159. esphome/components/mdns/mdns_libretiny.cpp +3 -3
  160. esphome/components/mdns/mdns_rp2040.cpp +3 -3
  161. esphome/components/mipi/__init__.py +1 -5
  162. esphome/components/mipi_spi/display.py +24 -8
  163. esphome/components/mipi_spi/mipi_spi.h +3 -3
  164. esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
  165. esphome/components/mmc5603/mmc5603.cpp +3 -3
  166. esphome/components/modbus/modbus.cpp +27 -13
  167. esphome/components/modbus/modbus.h +5 -3
  168. esphome/components/modbus/modbus_definitions.h +86 -0
  169. esphome/components/modbus_controller/__init__.py +29 -1
  170. esphome/components/modbus_controller/const.py +4 -0
  171. esphome/components/modbus_controller/modbus_controller.cpp +38 -13
  172. esphome/components/modbus_controller/modbus_controller.h +18 -29
  173. esphome/components/mpr121/mpr121.cpp +41 -42
  174. esphome/components/mpr121/mpr121.h +0 -1
  175. esphome/components/nau7802/nau7802.cpp +2 -2
  176. esphome/components/network/__init__.py +7 -3
  177. esphome/components/nextion/display.py +4 -4
  178. esphome/components/nextion/nextion.cpp +8 -8
  179. esphome/components/number/__init__.py +2 -0
  180. esphome/components/number/number_call.cpp +23 -12
  181. esphome/components/number/number_call.h +5 -0
  182. esphome/components/online_image/bmp_image.cpp +2 -1
  183. esphome/components/online_image/jpeg_image.cpp +4 -2
  184. esphome/components/openthread/openthread.cpp +6 -7
  185. esphome/components/openthread/openthread.h +0 -1
  186. esphome/components/ota/ota_backend.h +1 -0
  187. esphome/components/packages/__init__.py +10 -8
  188. esphome/components/packet_transport/packet_transport.cpp +2 -0
  189. esphome/components/pid/pid_controller.cpp +1 -1
  190. esphome/components/prometheus/prometheus_handler.cpp +239 -239
  191. esphome/components/psram/__init__.py +30 -28
  192. esphome/components/qmc5883l/qmc5883l.cpp +15 -0
  193. esphome/components/qmc5883l/qmc5883l.h +3 -0
  194. esphome/components/qmc5883l/sensor.py +31 -12
  195. esphome/components/remote_base/gobox_protocol.cpp +3 -3
  196. esphome/components/remote_receiver/__init__.py +14 -2
  197. esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
  198. esphome/components/remote_receiver/remote_receiver.h +4 -0
  199. esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
  200. esphome/components/remote_transmitter/__init__.py +2 -2
  201. esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
  202. esphome/components/rp2040/__init__.py +11 -11
  203. esphome/components/rtttl/rtttl.cpp +2 -2
  204. esphome/components/scd30/sensor.py +1 -1
  205. esphome/components/script/__init__.py +1 -1
  206. esphome/components/script/script.h +7 -7
  207. esphome/components/select/select.cpp +5 -4
  208. esphome/components/select/select_call.cpp +1 -1
  209. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  210. esphome/components/sensor/__init__.py +2 -0
  211. esphome/components/sha256/__init__.py +22 -0
  212. esphome/components/sha256/sha256.cpp +116 -0
  213. esphome/components/sha256/sha256.h +60 -0
  214. esphome/components/sim800l/sim800l.cpp +8 -4
  215. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  216. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  217. esphome/components/spi/__init__.py +0 -3
  218. esphome/components/split_buffer/__init__.py +5 -0
  219. esphome/components/split_buffer/split_buffer.cpp +133 -0
  220. esphome/components/split_buffer/split_buffer.h +40 -0
  221. esphome/components/sps30/sps30.cpp +14 -10
  222. esphome/components/sps30/sps30.h +2 -0
  223. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  224. esphome/components/st7789v/st7789v.cpp +3 -2
  225. esphome/components/statsd/statsd.cpp +1 -1
  226. esphome/components/substitutions/__init__.py +3 -1
  227. esphome/components/substitutions/jinja.py +13 -3
  228. esphome/components/sx126x/__init__.py +16 -0
  229. esphome/components/sx126x/sx126x.cpp +15 -1
  230. esphome/components/sx126x/sx126x.h +9 -1
  231. esphome/components/sx126x/sx126x_reg.h +2 -0
  232. esphome/components/text_sensor/text_sensor.cpp +16 -0
  233. esphome/components/text_sensor/text_sensor.h +3 -10
  234. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  235. esphome/components/tuya/select/tuya_select.cpp +1 -1
  236. esphome/components/tuya/tuya.cpp +29 -4
  237. esphome/components/uart/__init__.py +36 -26
  238. esphome/components/uart/uart.h +6 -0
  239. esphome/components/uart/uart_component.cpp +8 -0
  240. esphome/components/uart/uart_component.h +28 -0
  241. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  242. esphome/components/uart/uart_component_esp_idf.h +5 -2
  243. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  244. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  245. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  246. esphome/components/usb_host/__init__.py +2 -1
  247. esphome/components/usb_host/usb_host.h +82 -13
  248. esphome/components/usb_host/usb_host_client.cpp +180 -24
  249. esphome/components/usb_host/usb_host_component.cpp +1 -1
  250. esphome/components/usb_uart/__init__.py +0 -1
  251. esphome/components/usb_uart/ch34x.cpp +4 -4
  252. esphome/components/usb_uart/cp210x.cpp +3 -3
  253. esphome/components/usb_uart/usb_uart.cpp +88 -32
  254. esphome/components/usb_uart/usb_uart.h +30 -6
  255. esphome/components/valve/valve.cpp +1 -0
  256. esphome/components/veml7700/veml7700.cpp +7 -6
  257. esphome/components/version/version_text_sensor.cpp +2 -1
  258. esphome/components/voice_assistant/voice_assistant.cpp +3 -3
  259. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  260. esphome/components/web_server/list_entities.cpp +3 -4
  261. esphome/components/web_server/list_entities.h +8 -10
  262. esphome/components/web_server/ota/__init__.py +1 -1
  263. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  264. esphome/components/web_server/web_server.cpp +509 -404
  265. esphome/components/web_server/web_server.h +5 -6
  266. esphome/components/web_server/web_server_v1.cpp +21 -19
  267. esphome/components/web_server_base/__init__.py +5 -2
  268. esphome/components/web_server_base/web_server_base.h +27 -7
  269. esphome/components/web_server_idf/__init__.py +1 -1
  270. esphome/components/web_server_idf/multipart.cpp +2 -2
  271. esphome/components/web_server_idf/multipart.h +2 -2
  272. esphome/components/web_server_idf/utils.cpp +2 -2
  273. esphome/components/web_server_idf/utils.h +2 -2
  274. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  275. esphome/components/web_server_idf/web_server_idf.h +12 -10
  276. esphome/components/wifi/__init__.py +13 -11
  277. esphome/components/wifi/wifi_component.cpp +73 -56
  278. esphome/components/wifi/wifi_component.h +4 -4
  279. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  280. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  281. esphome/components/wireguard/__init__.py +1 -1
  282. esphome/components/wts01/__init__.py +0 -0
  283. esphome/components/wts01/sensor.py +41 -0
  284. esphome/components/wts01/wts01.cpp +91 -0
  285. esphome/components/wts01/wts01.h +27 -0
  286. esphome/components/zephyr/__init__.py +5 -5
  287. esphome/components/zwave_proxy/__init__.py +43 -0
  288. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  289. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  290. esphome/config.py +79 -24
  291. esphome/config_validation.py +13 -15
  292. esphome/const.py +9 -2
  293. esphome/core/__init__.py +31 -22
  294. esphome/core/component.cpp +28 -18
  295. esphome/core/component_iterator.h +2 -1
  296. esphome/core/config.py +15 -15
  297. esphome/core/defines.h +19 -0
  298. esphome/core/hash_base.h +56 -0
  299. esphome/core/helpers.cpp +19 -3
  300. esphome/core/helpers.h +26 -0
  301. esphome/core/scheduler.cpp +5 -21
  302. esphome/core/scheduler.h +19 -8
  303. esphome/core/string_ref.h +1 -1
  304. esphome/core/time.cpp +5 -5
  305. esphome/cpp_generator.py +4 -29
  306. esphome/dashboard/const.py +21 -4
  307. esphome/dashboard/core.py +10 -8
  308. esphome/dashboard/dns.py +15 -0
  309. esphome/dashboard/entries.py +15 -21
  310. esphome/dashboard/models.py +76 -0
  311. esphome/dashboard/settings.py +7 -7
  312. esphome/dashboard/status/mdns.py +46 -2
  313. esphome/dashboard/web_server.py +367 -93
  314. esphome/espota2.py +111 -31
  315. esphome/external_files.py +6 -7
  316. esphome/git.py +8 -0
  317. esphome/helpers.py +124 -77
  318. esphome/loader.py +8 -9
  319. esphome/platformio_api.py +25 -18
  320. esphome/storage_json.py +26 -21
  321. esphome/types.py +30 -2
  322. esphome/util.py +32 -16
  323. esphome/vscode.py +8 -8
  324. esphome/wizard.py +10 -10
  325. esphome/writer.py +50 -15
  326. esphome/yaml_util.py +37 -31
  327. esphome/zeroconf.py +12 -3
  328. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/METADATA +11 -11
  329. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/RECORD +333 -313
  330. esphome/components/event_emitter/__init__.py +0 -5
  331. esphome/components/event_emitter/event_emitter.cpp +0 -14
  332. esphome/components/event_emitter/event_emitter.h +0 -63
  333. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  334. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  335. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  336. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  337. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  338. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  339. esphome/core/string_ref.cpp +0 -12
  340. esphome/dashboard/util/file.py +0 -63
  341. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/WHEEL +0 -0
  342. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/entry_points.txt +0 -0
  343. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/licenses/LICENSE +0 -0
  344. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,346 @@
1
+ #include "zwave_proxy.h"
2
+ #include "esphome/components/api/api_server.h"
3
+ #include "esphome/core/application.h"
4
+ #include "esphome/core/helpers.h"
5
+ #include "esphome/core/log.h"
6
+ #include "esphome/core/util.h"
7
+
8
+ namespace esphome {
9
+ namespace zwave_proxy {
10
+
11
+ static const char *const TAG = "zwave_proxy";
12
+
13
+ static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20;
14
+ // GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
15
+ static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value
16
+ static constexpr uint8_t ZWAVE_MIN_GET_NETWORK_IDS_LENGTH = 9; // TYPE + CMD + HOME_ID(4) + NODE_ID + checksum
17
+ static constexpr uint32_t HOME_ID_TIMEOUT_MS = 100; // Timeout for waiting for home ID during setup
18
+
19
+ static uint8_t calculate_frame_checksum(const uint8_t *data, uint8_t length) {
20
+ // Calculate Z-Wave frame checksum
21
+ // XOR all bytes between SOF and checksum position (exclusive)
22
+ // Initial value is 0xFF per Z-Wave protocol specification
23
+ uint8_t checksum = 0xFF;
24
+ for (uint8_t i = 1; i < length - 1; i++) {
25
+ checksum ^= data[i];
26
+ }
27
+ return checksum;
28
+ }
29
+
30
+ ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; }
31
+
32
+ void ZWaveProxy::setup() {
33
+ this->setup_time_ = App.get_loop_component_start_time();
34
+ this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS);
35
+ }
36
+
37
+ float ZWaveProxy::get_setup_priority() const {
38
+ // Set up before API so home ID is ready when API starts
39
+ return setup_priority::BEFORE_CONNECTION;
40
+ }
41
+
42
+ bool ZWaveProxy::can_proceed() {
43
+ // If we already have the home ID, we can proceed
44
+ if (this->home_id_ready_) {
45
+ return true;
46
+ }
47
+
48
+ // Handle any pending responses
49
+ if (this->response_handler_()) {
50
+ ESP_LOGV(TAG, "Handled response during setup");
51
+ }
52
+
53
+ // Process UART data to check for home ID
54
+ this->process_uart_();
55
+
56
+ // Check if we got the home ID after processing
57
+ if (this->home_id_ready_) {
58
+ return true;
59
+ }
60
+
61
+ // Wait up to HOME_ID_TIMEOUT_MS for home ID response
62
+ const uint32_t now = App.get_loop_component_start_time();
63
+ if (now - this->setup_time_ > HOME_ID_TIMEOUT_MS) {
64
+ ESP_LOGW(TAG, "Timeout reading Home ID during setup");
65
+ return true; // Proceed anyway after timeout
66
+ }
67
+
68
+ return false; // Keep waiting
69
+ }
70
+
71
+ void ZWaveProxy::loop() {
72
+ if (this->response_handler_()) {
73
+ ESP_LOGV(TAG, "Handled late response");
74
+ }
75
+ if (this->api_connection_ != nullptr && (!this->api_connection_->is_connection_setup() || !api_is_connected())) {
76
+ ESP_LOGW(TAG, "Subscriber disconnected");
77
+ this->api_connection_ = nullptr; // Unsubscribe if disconnected
78
+ }
79
+
80
+ this->process_uart_();
81
+ this->status_clear_warning();
82
+ }
83
+
84
+ void ZWaveProxy::process_uart_() {
85
+ while (this->available()) {
86
+ uint8_t byte;
87
+ if (!this->read_byte(&byte)) {
88
+ this->status_set_warning("UART read failed");
89
+ return;
90
+ }
91
+ if (this->parse_byte_(byte)) {
92
+ // Check if this is a GET_NETWORK_IDS response frame
93
+ // Frame format: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
94
+ // We verify:
95
+ // - buffer_[0]: Start of frame marker (0x01)
96
+ // - buffer_[1]: Length field must be >= 9 to contain all required data
97
+ // - buffer_[2]: Command type (0x01 for response)
98
+ // - buffer_[3]: Command ID (0x20 for GET_NETWORK_IDS)
99
+ if (this->buffer_[3] == ZWAVE_COMMAND_GET_NETWORK_IDS && this->buffer_[2] == ZWAVE_COMMAND_TYPE_RESPONSE &&
100
+ this->buffer_[1] >= ZWAVE_MIN_GET_NETWORK_IDS_LENGTH && this->buffer_[0] == ZWAVE_FRAME_TYPE_START) {
101
+ // Store the 4-byte Home ID, which starts at offset 4, and notify connected clients if it changed
102
+ // The frame parser has already validated the checksum and ensured all bytes are present
103
+ if (this->set_home_id(&this->buffer_[4])) {
104
+ this->send_homeid_changed_msg_();
105
+ }
106
+ }
107
+ ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr));
108
+ if (this->api_connection_ != nullptr) {
109
+ // Zero-copy: point directly to our buffer
110
+ this->outgoing_proto_msg_.data = this->buffer_.data();
111
+ if (this->in_bootloader_) {
112
+ this->outgoing_proto_msg_.data_len = this->buffer_index_;
113
+ } else {
114
+ // If this is a data frame, use frame length indicator + 2 (for SoF + checksum), else assume 1 for ACK/NAK/CAN
115
+ this->outgoing_proto_msg_.data_len = this->buffer_[0] == ZWAVE_FRAME_TYPE_START ? this->buffer_[1] + 2 : 1;
116
+ }
117
+ this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ void ZWaveProxy::dump_config() {
124
+ ESP_LOGCONFIG(TAG,
125
+ "Z-Wave Proxy:\n"
126
+ " Home ID: %s",
127
+ format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
128
+ }
129
+
130
+ void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) {
131
+ if (this->home_id_ready_) {
132
+ // If a client just authenticated & HomeID is ready, send the current HomeID
133
+ this->send_homeid_changed_msg_(conn);
134
+ }
135
+ }
136
+
137
+ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type) {
138
+ switch (type) {
139
+ case api::enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE:
140
+ if (this->api_connection_ != nullptr) {
141
+ ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
142
+ return;
143
+ }
144
+ this->api_connection_ = api_connection;
145
+ ESP_LOGV(TAG, "API connection is now subscribed");
146
+ break;
147
+ case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
148
+ if (this->api_connection_ != api_connection) {
149
+ ESP_LOGV(TAG, "API connection is not subscribed");
150
+ return;
151
+ }
152
+ this->api_connection_ = nullptr;
153
+ break;
154
+ default:
155
+ ESP_LOGW(TAG, "Unknown request type: %d", type);
156
+ break;
157
+ }
158
+ }
159
+
160
+ bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) {
161
+ if (std::memcmp(this->home_id_.data(), new_home_id, this->home_id_.size()) == 0) {
162
+ ESP_LOGV(TAG, "Home ID unchanged");
163
+ return false; // No change
164
+ }
165
+ std::memcpy(this->home_id_.data(), new_home_id, this->home_id_.size());
166
+ ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
167
+ this->home_id_ready_ = true;
168
+ return true; // Home ID was changed
169
+ }
170
+
171
+ void ZWaveProxy::send_frame(const uint8_t *data, size_t length) {
172
+ if (length == 1 && data[0] == this->last_response_) {
173
+ ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]);
174
+ return;
175
+ }
176
+ ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str());
177
+ this->write_array(data, length);
178
+ }
179
+
180
+ void ZWaveProxy::send_homeid_changed_msg_(api::APIConnection *conn) {
181
+ api::ZWaveProxyRequest msg;
182
+ msg.type = api::enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE;
183
+ msg.data = this->home_id_.data();
184
+ msg.data_len = this->home_id_.size();
185
+ if (conn != nullptr) {
186
+ // Send to specific connection
187
+ conn->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
188
+ } else if (api::global_api_server != nullptr) {
189
+ // We could add code to manage a second subscription type, but, since this message is
190
+ // very infrequent and small, we simply send it to all clients
191
+ api::global_api_server->on_zwave_proxy_request(msg);
192
+ }
193
+ }
194
+
195
+ void ZWaveProxy::send_simple_command_(const uint8_t command_id) {
196
+ // Send a simple Z-Wave command with no parameters
197
+ // Frame format: [SOF][LENGTH][TYPE][CMD][CHECKSUM]
198
+ // Where LENGTH=0x03 (3 bytes: TYPE + CMD + CHECKSUM)
199
+ uint8_t cmd[] = {0x01, 0x03, 0x00, command_id, 0x00};
200
+ cmd[4] = calculate_frame_checksum(cmd, sizeof(cmd));
201
+ this->send_frame(cmd, sizeof(cmd));
202
+ }
203
+
204
+ bool ZWaveProxy::parse_byte_(uint8_t byte) {
205
+ bool frame_completed = false;
206
+ // Basic parsing logic for received frames
207
+ switch (this->parsing_state_) {
208
+ case ZWAVE_PARSING_STATE_WAIT_START:
209
+ this->parse_start_(byte);
210
+ break;
211
+ case ZWAVE_PARSING_STATE_WAIT_LENGTH:
212
+ if (!byte) {
213
+ ESP_LOGW(TAG, "Invalid LENGTH: %u", byte);
214
+ this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK;
215
+ return false;
216
+ }
217
+ ESP_LOGVV(TAG, "Received LENGTH: %u", byte);
218
+ this->end_frame_after_ = this->buffer_index_ + byte;
219
+ ESP_LOGVV(TAG, "Calculated EOF: %u", this->end_frame_after_);
220
+ this->buffer_[this->buffer_index_++] = byte;
221
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_TYPE;
222
+ break;
223
+ case ZWAVE_PARSING_STATE_WAIT_TYPE:
224
+ this->buffer_[this->buffer_index_++] = byte;
225
+ ESP_LOGVV(TAG, "Received TYPE: 0x%02X", byte);
226
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_COMMAND_ID;
227
+ break;
228
+ case ZWAVE_PARSING_STATE_WAIT_COMMAND_ID:
229
+ this->buffer_[this->buffer_index_++] = byte;
230
+ ESP_LOGVV(TAG, "Received COMMAND ID: 0x%02X", byte);
231
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_PAYLOAD;
232
+ break;
233
+ case ZWAVE_PARSING_STATE_WAIT_PAYLOAD:
234
+ this->buffer_[this->buffer_index_++] = byte;
235
+ ESP_LOGVV(TAG, "Received PAYLOAD: 0x%02X", byte);
236
+ if (this->buffer_index_ >= this->end_frame_after_) {
237
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_CHECKSUM;
238
+ }
239
+ break;
240
+ case ZWAVE_PARSING_STATE_WAIT_CHECKSUM: {
241
+ this->buffer_[this->buffer_index_++] = byte;
242
+ auto checksum = calculate_frame_checksum(this->buffer_.data(), this->buffer_index_);
243
+ ESP_LOGVV(TAG, "CHECKSUM Received: 0x%02X - Calculated: 0x%02X", byte, checksum);
244
+ if (checksum != byte) {
245
+ ESP_LOGW(TAG, "Bad checksum: expected 0x%02X, got 0x%02X", checksum, byte);
246
+ this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK;
247
+ } else {
248
+ this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK;
249
+ ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_.data(), this->buffer_index_).c_str());
250
+ frame_completed = true;
251
+ }
252
+ this->response_handler_();
253
+ break;
254
+ }
255
+ case ZWAVE_PARSING_STATE_READ_BL_MENU:
256
+ this->buffer_[this->buffer_index_++] = byte;
257
+ if (!byte) {
258
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
259
+ frame_completed = true;
260
+ }
261
+ break;
262
+ case ZWAVE_PARSING_STATE_SEND_ACK:
263
+ case ZWAVE_PARSING_STATE_SEND_NAK:
264
+ break; // Should not happen, handled in loop()
265
+ default:
266
+ ESP_LOGW(TAG, "Bad parsing state; resetting");
267
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
268
+ break;
269
+ }
270
+ return frame_completed;
271
+ }
272
+
273
+ void ZWaveProxy::parse_start_(uint8_t byte) {
274
+ this->buffer_index_ = 0;
275
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
276
+ switch (byte) {
277
+ case ZWAVE_FRAME_TYPE_START:
278
+ ESP_LOGVV(TAG, "Received START");
279
+ if (this->in_bootloader_) {
280
+ ESP_LOGD(TAG, "Exited bootloader mode");
281
+ this->in_bootloader_ = false;
282
+ }
283
+ this->buffer_[this->buffer_index_++] = byte;
284
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_LENGTH;
285
+ return;
286
+ case ZWAVE_FRAME_TYPE_BL_MENU:
287
+ ESP_LOGVV(TAG, "Received BL_MENU");
288
+ if (!this->in_bootloader_) {
289
+ ESP_LOGD(TAG, "Entered bootloader mode");
290
+ this->in_bootloader_ = true;
291
+ }
292
+ this->buffer_[this->buffer_index_++] = byte;
293
+ this->parsing_state_ = ZWAVE_PARSING_STATE_READ_BL_MENU;
294
+ return;
295
+ case ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD:
296
+ ESP_LOGVV(TAG, "Received BL_BEGIN_UPLOAD");
297
+ break;
298
+ case ZWAVE_FRAME_TYPE_ACK:
299
+ ESP_LOGVV(TAG, "Received ACK");
300
+ break;
301
+ case ZWAVE_FRAME_TYPE_NAK:
302
+ ESP_LOGW(TAG, "Received NAK");
303
+ break;
304
+ case ZWAVE_FRAME_TYPE_CAN:
305
+ ESP_LOGW(TAG, "Received CAN");
306
+ break;
307
+ default:
308
+ ESP_LOGW(TAG, "Unrecognized START: 0x%02X", byte);
309
+ return;
310
+ }
311
+ // Forward response (ACK/NAK/CAN) back to client for processing
312
+ if (this->api_connection_ != nullptr) {
313
+ // Store single byte in buffer and point to it
314
+ this->buffer_[0] = byte;
315
+ this->outgoing_proto_msg_.data = this->buffer_.data();
316
+ this->outgoing_proto_msg_.data_len = 1;
317
+ this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE);
318
+ }
319
+ }
320
+
321
+ bool ZWaveProxy::response_handler_() {
322
+ switch (this->parsing_state_) {
323
+ case ZWAVE_PARSING_STATE_SEND_ACK:
324
+ this->last_response_ = ZWAVE_FRAME_TYPE_ACK;
325
+ break;
326
+ case ZWAVE_PARSING_STATE_SEND_CAN:
327
+ this->last_response_ = ZWAVE_FRAME_TYPE_CAN;
328
+ break;
329
+ case ZWAVE_PARSING_STATE_SEND_NAK:
330
+ this->last_response_ = ZWAVE_FRAME_TYPE_NAK;
331
+ break;
332
+ default:
333
+ return false; // No response handled
334
+ }
335
+
336
+ ESP_LOGVV(TAG, "Sending %s (0x%02X)", this->last_response_ == ZWAVE_FRAME_TYPE_ACK ? "ACK" : "NAK/CAN",
337
+ this->last_response_);
338
+ this->write_byte(this->last_response_);
339
+ this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
340
+ return true;
341
+ }
342
+
343
+ ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
344
+
345
+ } // namespace zwave_proxy
346
+ } // namespace esphome
@@ -0,0 +1,93 @@
1
+ #pragma once
2
+
3
+ #include "esphome/components/api/api_connection.h"
4
+ #include "esphome/components/api/api_pb2.h"
5
+ #include "esphome/core/component.h"
6
+ #include "esphome/core/helpers.h"
7
+ #include "esphome/components/uart/uart.h"
8
+
9
+ #include <array>
10
+
11
+ namespace esphome {
12
+ namespace zwave_proxy {
13
+
14
+ static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size
15
+
16
+ enum ZWaveResponseTypes : uint8_t {
17
+ ZWAVE_FRAME_TYPE_ACK = 0x06,
18
+ ZWAVE_FRAME_TYPE_CAN = 0x18,
19
+ ZWAVE_FRAME_TYPE_NAK = 0x15,
20
+ ZWAVE_FRAME_TYPE_START = 0x01,
21
+ ZWAVE_FRAME_TYPE_BL_MENU = 0x0D,
22
+ ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD = 0x43,
23
+ };
24
+
25
+ enum ZWaveParsingState : uint8_t {
26
+ ZWAVE_PARSING_STATE_WAIT_START,
27
+ ZWAVE_PARSING_STATE_WAIT_LENGTH,
28
+ ZWAVE_PARSING_STATE_WAIT_TYPE,
29
+ ZWAVE_PARSING_STATE_WAIT_COMMAND_ID,
30
+ ZWAVE_PARSING_STATE_WAIT_PAYLOAD,
31
+ ZWAVE_PARSING_STATE_WAIT_CHECKSUM,
32
+ ZWAVE_PARSING_STATE_SEND_ACK,
33
+ ZWAVE_PARSING_STATE_SEND_CAN,
34
+ ZWAVE_PARSING_STATE_SEND_NAK,
35
+ ZWAVE_PARSING_STATE_READ_BL_MENU,
36
+ };
37
+
38
+ enum ZWaveProxyFeature : uint32_t {
39
+ FEATURE_ZWAVE_PROXY_ENABLED = 1 << 0,
40
+ };
41
+
42
+ class ZWaveProxy : public uart::UARTDevice, public Component {
43
+ public:
44
+ ZWaveProxy();
45
+
46
+ void setup() override;
47
+ void loop() override;
48
+ void dump_config() override;
49
+ float get_setup_priority() const override;
50
+ bool can_proceed() override;
51
+
52
+ void api_connection_authenticated(api::APIConnection *conn);
53
+ void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type);
54
+ api::APIConnection *get_api_connection() { return this->api_connection_; }
55
+
56
+ uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; }
57
+ uint32_t get_home_id() {
58
+ return encode_uint32(this->home_id_[0], this->home_id_[1], this->home_id_[2], this->home_id_[3]);
59
+ }
60
+ bool set_home_id(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed.
61
+
62
+ void send_frame(const uint8_t *data, size_t length);
63
+
64
+ protected:
65
+ void send_homeid_changed_msg_(api::APIConnection *conn = nullptr);
66
+ void send_simple_command_(uint8_t command_id);
67
+ bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer)
68
+ void parse_start_(uint8_t byte);
69
+ bool response_handler_();
70
+ void process_uart_(); // Process all available UART data
71
+
72
+ // Pre-allocated message - always ready to send
73
+ api::ZWaveProxyFrame outgoing_proto_msg_;
74
+ std::array<uint8_t, MAX_ZWAVE_FRAME_SIZE> buffer_; // Fixed buffer for incoming data
75
+ std::array<uint8_t, 4> home_id_{0, 0, 0, 0}; // Fixed buffer for home ID
76
+
77
+ // Pointers and 32-bit values (aligned together)
78
+ api::APIConnection *api_connection_{nullptr}; // Current subscribed client
79
+ uint32_t setup_time_{0}; // Time when setup() was called
80
+
81
+ // 8-bit values (grouped together to minimize padding)
82
+ uint8_t buffer_index_{0}; // Index for populating the data buffer
83
+ uint8_t end_frame_after_{0}; // Payload reception ends after this index
84
+ uint8_t last_response_{0}; // Last response type sent
85
+ ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START};
86
+ bool in_bootloader_{false}; // True if the device is detected to be in bootloader mode
87
+ bool home_id_ready_{false}; // True when home ID has been received from Z-Wave module
88
+ };
89
+
90
+ extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
91
+
92
+ } // namespace zwave_proxy
93
+ } // namespace esphome
esphome/config.py CHANGED
@@ -32,7 +32,7 @@ from esphome.log import AnsiFore, color
32
32
  from esphome.types import ConfigFragmentType, ConfigType
33
33
  from esphome.util import OrderedDict, safe_print
34
34
  from esphome.voluptuous_schema import ExtraKeysInvalid
35
- from esphome.yaml_util import ESPForceValue, ESPHomeDataBase, is_secret
35
+ from esphome.yaml_util import ESPHomeDataBase, ESPLiteralValue, is_secret
36
36
 
37
37
  _LOGGER = logging.getLogger(__name__)
38
38
 
@@ -67,6 +67,31 @@ ConfigPath = list[str | int]
67
67
  path_context = contextvars.ContextVar("Config path")
68
68
 
69
69
 
70
+ def _add_auto_load_steps(result: Config, loads: list[str]) -> None:
71
+ """Add AutoLoadValidationStep for each component in loads that isn't already loaded."""
72
+ for load in loads:
73
+ if load not in result:
74
+ result.add_validation_step(AutoLoadValidationStep(load))
75
+
76
+
77
+ def _process_auto_load(
78
+ result: Config, platform: ComponentManifest, path: ConfigPath
79
+ ) -> None:
80
+ # Process platform's AUTO_LOAD
81
+ auto_load = platform.auto_load
82
+ if isinstance(auto_load, list):
83
+ _add_auto_load_steps(result, auto_load)
84
+ elif callable(auto_load):
85
+ import inspect
86
+
87
+ if inspect.signature(auto_load).parameters:
88
+ result.add_validation_step(
89
+ AddDynamicAutoLoadsValidationStep(path, platform)
90
+ )
91
+ else:
92
+ _add_auto_load_steps(result, auto_load())
93
+
94
+
70
95
  def _process_platform_config(
71
96
  result: Config,
72
97
  component_name: str,
@@ -91,9 +116,7 @@ def _process_platform_config(
91
116
  CORE.loaded_platforms.add(f"{component_name}/{platform_name}")
92
117
 
93
118
  # Process platform's AUTO_LOAD
94
- for load in platform.auto_load:
95
- if load not in result:
96
- result.add_validation_step(AutoLoadValidationStep(load))
119
+ _process_auto_load(result, platform, path)
97
120
 
98
121
  # Add validation steps for the platform
99
122
  p_domain = f"{component_name}.{platform_name}"
@@ -306,7 +329,7 @@ def recursive_check_replaceme(value):
306
329
  return cv.Schema([recursive_check_replaceme])(value)
307
330
  if isinstance(value, dict):
308
331
  return cv.Schema({cv.valid: recursive_check_replaceme})(value)
309
- if isinstance(value, ESPForceValue):
332
+ if isinstance(value, ESPLiteralValue):
310
333
  pass
311
334
  if isinstance(value, str) and value == "REPLACEME":
312
335
  raise cv.Invalid(
@@ -314,7 +337,7 @@ def recursive_check_replaceme(value):
314
337
  "Please make sure you have replaced all fields from the sample "
315
338
  "configuration.\n"
316
339
  "If you want to use the literal REPLACEME string, "
317
- 'please use "!force REPLACEME"'
340
+ 'please use "!literal REPLACEME"'
318
341
  )
319
342
  return value
320
343
 
@@ -382,11 +405,15 @@ class LoadValidationStep(ConfigValidationStep):
382
405
  result.add_str_error(f"Component not found: {self.domain}", path)
383
406
  return
384
407
  CORE.loaded_integrations.add(self.domain)
408
+ # For platform components, normalize conf before creating MetadataValidationStep
409
+ if component.is_platform_component:
410
+ if not self.conf:
411
+ result[self.domain] = self.conf = []
412
+ elif not isinstance(self.conf, list):
413
+ result[self.domain] = self.conf = [self.conf]
385
414
 
386
415
  # Process AUTO_LOAD
387
- for load in component.auto_load:
388
- if load not in result:
389
- result.add_validation_step(AutoLoadValidationStep(load))
416
+ _process_auto_load(result, component, path)
390
417
 
391
418
  result.add_validation_step(
392
419
  MetadataValidationStep([self.domain], self.domain, self.conf, component)
@@ -399,12 +426,6 @@ class LoadValidationStep(ConfigValidationStep):
399
426
  # Remove this is as an output path
400
427
  result.remove_output_path([self.domain], self.domain)
401
428
 
402
- # Ensure conf is a list
403
- if not self.conf:
404
- result[self.domain] = self.conf = []
405
- elif not isinstance(self.conf, list):
406
- result[self.domain] = self.conf = [self.conf]
407
-
408
429
  for i, p_config in enumerate(self.conf):
409
430
  path = [self.domain, i]
410
431
  # Construct temporary unknown output path
@@ -618,6 +639,34 @@ class MetadataValidationStep(ConfigValidationStep):
618
639
  result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
619
640
 
620
641
 
642
+ class AddDynamicAutoLoadsValidationStep(ConfigValidationStep):
643
+ """Add dynamic auto loads step.
644
+
645
+ This step is used to auto-load components where one component can alter its
646
+ AUTO_LOAD based on its configuration.
647
+ """
648
+
649
+ # Has to happen after normal schema is validated and before final schema validation
650
+ priority = -5.0
651
+
652
+ def __init__(self, path: ConfigPath, comp: ComponentManifest) -> None:
653
+ self.path = path
654
+ self.comp = comp
655
+
656
+ def run(self, result: Config) -> None:
657
+ if result.errors:
658
+ # If result already has errors, skip this step
659
+ return
660
+
661
+ conf = result.get_nested_item(self.path)
662
+ with result.catch_error(self.path):
663
+ auto_load = self.comp.auto_load
664
+ if not callable(auto_load):
665
+ return
666
+ loads = auto_load(conf)
667
+ _add_auto_load_steps(result, loads)
668
+
669
+
621
670
  class SchemaValidationStep(ConfigValidationStep):
622
671
  """Schema validation step.
623
672
 
@@ -846,7 +895,9 @@ class PinUseValidationCheck(ConfigValidationStep):
846
895
 
847
896
 
848
897
  def validate_config(
849
- config: dict[str, Any], command_line_substitutions: dict[str, Any]
898
+ config: dict[str, Any],
899
+ command_line_substitutions: dict[str, Any],
900
+ skip_external_update: bool = False,
850
901
  ) -> Config:
851
902
  result = Config()
852
903
 
@@ -859,7 +910,7 @@ def validate_config(
859
910
 
860
911
  result.add_output_path([CONF_PACKAGES], CONF_PACKAGES)
861
912
  try:
862
- config = do_packages_pass(config)
913
+ config = do_packages_pass(config, skip_update=skip_external_update)
863
914
  except vol.Invalid as err:
864
915
  result.update(config)
865
916
  result.add_error(err)
@@ -896,7 +947,7 @@ def validate_config(
896
947
 
897
948
  result.add_output_path([CONF_EXTERNAL_COMPONENTS], CONF_EXTERNAL_COMPONENTS)
898
949
  try:
899
- do_external_components_pass(config)
950
+ do_external_components_pass(config, skip_update=skip_external_update)
900
951
  except vol.Invalid as err:
901
952
  result.update(config)
902
953
  result.add_error(err)
@@ -1020,7 +1071,9 @@ class InvalidYAMLError(EsphomeError):
1020
1071
  self.base_exc = base_exc
1021
1072
 
1022
1073
 
1023
- def _load_config(command_line_substitutions: dict[str, Any]) -> Config:
1074
+ def _load_config(
1075
+ command_line_substitutions: dict[str, Any], skip_external_update: bool = False
1076
+ ) -> Config:
1024
1077
  """Load the configuration file."""
1025
1078
  try:
1026
1079
  config = yaml_util.load_yaml(CORE.config_path)
@@ -1028,7 +1081,7 @@ def _load_config(command_line_substitutions: dict[str, Any]) -> Config:
1028
1081
  raise InvalidYAMLError(e) from e
1029
1082
 
1030
1083
  try:
1031
- return validate_config(config, command_line_substitutions)
1084
+ return validate_config(config, command_line_substitutions, skip_external_update)
1032
1085
  except EsphomeError:
1033
1086
  raise
1034
1087
  except Exception:
@@ -1036,9 +1089,11 @@ def _load_config(command_line_substitutions: dict[str, Any]) -> Config:
1036
1089
  raise
1037
1090
 
1038
1091
 
1039
- def load_config(command_line_substitutions: dict[str, Any]) -> Config:
1092
+ def load_config(
1093
+ command_line_substitutions: dict[str, Any], skip_external_update: bool = False
1094
+ ) -> Config:
1040
1095
  try:
1041
- return _load_config(command_line_substitutions)
1096
+ return _load_config(command_line_substitutions, skip_external_update)
1042
1097
  except vol.Invalid as err:
1043
1098
  raise EsphomeError(f"Error while parsing config: {err}") from err
1044
1099
 
@@ -1178,10 +1233,10 @@ def strip_default_ids(config):
1178
1233
  return config
1179
1234
 
1180
1235
 
1181
- def read_config(command_line_substitutions):
1236
+ def read_config(command_line_substitutions, skip_external_update=False):
1182
1237
  _LOGGER.info("Reading configuration %s...", CORE.config_path)
1183
1238
  try:
1184
- res = load_config(command_line_substitutions)
1239
+ res = load_config(command_line_substitutions, skip_external_update)
1185
1240
  except EsphomeError as err:
1186
1241
  _LOGGER.error("Error while reading config: %s", err)
1187
1242
  return None