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
esphome/__main__.py CHANGED
@@ -6,6 +6,7 @@ import getpass
6
6
  import importlib
7
7
  import logging
8
8
  import os
9
+ from pathlib import Path
9
10
  import re
10
11
  import sys
11
12
  import time
@@ -13,9 +14,11 @@ from typing import Protocol
13
14
 
14
15
  import argcomplete
15
16
 
17
+ # Note: Do not import modules from esphome.components here, as this would
18
+ # cause them to be loaded before external components are processed, resulting
19
+ # in the built-in version being used instead of the external component one.
16
20
  from esphome import const, writer, yaml_util
17
21
  import esphome.codegen as cg
18
- from esphome.components.mqtt import CONF_DISCOVER_IP
19
22
  from esphome.config import iter_component_configs, read_config, strip_default_ids
20
23
  from esphome.const import (
21
24
  ALLOWED_NAME_CHARS,
@@ -114,6 +117,14 @@ class Purpose(StrEnum):
114
117
  LOGGING = "logging"
115
118
 
116
119
 
120
+ def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
121
+ """Resolve an address using cache if available, otherwise return the address itself."""
122
+ if CORE.address_cache and (cached := CORE.address_cache.get_addresses(address)):
123
+ _LOGGER.debug("Using cached addresses for %s: %s", purpose.value, cached)
124
+ return cached
125
+ return [address]
126
+
127
+
117
128
  def choose_upload_log_host(
118
129
  default: list[str] | str | None,
119
130
  check_default: str | None,
@@ -142,7 +153,7 @@ def choose_upload_log_host(
142
153
  (purpose == Purpose.LOGGING and has_api())
143
154
  or (purpose == Purpose.UPLOADING and has_ota())
144
155
  ):
145
- resolved.append(CORE.address)
156
+ resolved.extend(_resolve_with_cache(CORE.address, purpose))
146
157
 
147
158
  if purpose == Purpose.LOGGING:
148
159
  if has_api() and has_mqtt_ip_lookup():
@@ -152,15 +163,14 @@ def choose_upload_log_host(
152
163
  resolved.append("MQTT")
153
164
 
154
165
  if has_api() and has_non_ip_address():
155
- resolved.append(CORE.address)
166
+ resolved.extend(_resolve_with_cache(CORE.address, purpose))
156
167
 
157
168
  elif purpose == Purpose.UPLOADING:
158
169
  if has_ota() and has_mqtt_ip_lookup():
159
170
  resolved.append("MQTTIP")
160
171
 
161
172
  if has_ota() and has_non_ip_address():
162
- resolved.append(CORE.address)
163
-
173
+ resolved.extend(_resolve_with_cache(CORE.address, purpose))
164
174
  else:
165
175
  resolved.append(device)
166
176
  if not resolved:
@@ -232,6 +242,8 @@ def has_ota() -> bool:
232
242
 
233
243
  def has_mqtt_ip_lookup() -> bool:
234
244
  """Check if MQTT is available and IP lookup is supported."""
245
+ from esphome.components.mqtt import CONF_DISCOVER_IP
246
+
235
247
  if CONF_MQTT not in CORE.config:
236
248
  return False
237
249
  # Default Enabled
@@ -445,7 +457,7 @@ def upload_using_esptool(
445
457
  "detect",
446
458
  ]
447
459
  for img in flash_images:
448
- cmd += [img.offset, img.path]
460
+ cmd += [img.offset, str(img.path)]
449
461
 
450
462
  if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
451
463
  import esptool
@@ -531,7 +543,10 @@ def upload_program(
531
543
 
532
544
  remote_port = int(ota_conf[CONF_PORT])
533
545
  password = ota_conf.get(CONF_PASSWORD, "")
534
- binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
546
+ if getattr(args, "file", None) is not None:
547
+ binary = Path(args.file)
548
+ else:
549
+ binary = CORE.firmware_bin
535
550
 
536
551
  # MQTT address resolution
537
552
  if get_port_type(host) in ("MQTT", "MQTTIP"):
@@ -598,7 +613,7 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
598
613
  def command_wizard(args: ArgsProtocol) -> int | None:
599
614
  from esphome import wizard
600
615
 
601
- return wizard.wizard(args.configuration)
616
+ return wizard.wizard(Path(args.configuration))
602
617
 
603
618
 
604
619
  def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
@@ -720,6 +735,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
720
735
  return clean_mqtt(config, args)
721
736
 
722
737
 
738
+ def command_clean_all(args: ArgsProtocol) -> int | None:
739
+ try:
740
+ writer.clean_all(args.configuration)
741
+ except OSError as err:
742
+ _LOGGER.error("Error cleaning all files: %s", err)
743
+ return 1
744
+ _LOGGER.info("Done!")
745
+ return 0
746
+
747
+
723
748
  def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
724
749
  from esphome import mqtt
725
750
 
@@ -761,7 +786,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
761
786
  safe_print(f"{half_line}{middle_text}{half_line}")
762
787
 
763
788
  for f in files:
764
- safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
789
+ safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
765
790
  safe_print("-" * twidth)
766
791
  safe_print()
767
792
  if CORE.dashboard:
@@ -773,10 +798,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
773
798
  "esphome", "run", f, "--no-logs", "--device", "OTA"
774
799
  )
775
800
  if rc == 0:
776
- print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
801
+ print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
777
802
  success[f] = True
778
803
  else:
779
- print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
804
+ print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
780
805
  success[f] = False
781
806
 
782
807
  safe_print()
@@ -787,9 +812,9 @@ def command_update_all(args: ArgsProtocol) -> int | None:
787
812
  failed = 0
788
813
  for f in files:
789
814
  if success[f]:
790
- safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
815
+ safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
791
816
  else:
792
- safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
817
+ safe_print(f" - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
793
818
  failed += 1
794
819
  return failed
795
820
 
@@ -811,7 +836,8 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
811
836
 
812
837
 
813
838
  def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
814
- for c in args.name:
839
+ new_name = args.name
840
+ for c in new_name:
815
841
  if c not in ALLOWED_NAME_CHARS:
816
842
  print(
817
843
  color(
@@ -822,8 +848,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
822
848
  )
823
849
  return 1
824
850
  # Load existing yaml file
825
- with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
826
- raw_contents = raw_file.read()
851
+ raw_contents = CORE.config_path.read_text(encoding="utf-8")
827
852
 
828
853
  yaml = yaml_util.load_yaml(CORE.config_path)
829
854
  if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
@@ -838,7 +863,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
838
863
  if match is None:
839
864
  new_raw = re.sub(
840
865
  rf"name:\s+[\"']?{old_name}[\"']?",
841
- f'name: "{args.name}"',
866
+ f'name: "{new_name}"',
842
867
  raw_contents,
843
868
  )
844
869
  else:
@@ -858,29 +883,28 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
858
883
 
859
884
  new_raw = re.sub(
860
885
  rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
861
- f'\\1: "{args.name}"',
886
+ f'\\1: "{new_name}"',
862
887
  raw_contents,
863
888
  flags=re.MULTILINE,
864
889
  )
865
890
 
866
- new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
891
+ new_path: Path = CORE.config_dir / (new_name + ".yaml")
867
892
  print(
868
- f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
893
+ f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
869
894
  )
870
895
  print()
871
896
 
872
- with open(new_path, mode="w", encoding="utf-8") as new_file:
873
- new_file.write(new_raw)
897
+ new_path.write_text(new_raw, encoding="utf-8")
874
898
 
875
- rc = run_external_process("esphome", "config", new_path)
899
+ rc = run_external_process("esphome", "config", str(new_path))
876
900
  if rc != 0:
877
901
  print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
878
- os.remove(new_path)
902
+ new_path.unlink()
879
903
  return 1
880
904
 
881
905
  cli_args = [
882
906
  "run",
883
- new_path,
907
+ str(new_path),
884
908
  "--no-logs",
885
909
  "--device",
886
910
  CORE.address,
@@ -894,11 +918,11 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
894
918
  except KeyboardInterrupt:
895
919
  rc = 1
896
920
  if rc != 0:
897
- os.remove(new_path)
921
+ new_path.unlink()
898
922
  return 1
899
923
 
900
924
  if CORE.config_path != new_path:
901
- os.remove(CORE.config_path)
925
+ CORE.config_path.unlink()
902
926
 
903
927
  print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
904
928
  print()
@@ -911,6 +935,7 @@ PRE_CONFIG_ACTIONS = {
911
935
  "dashboard": command_dashboard,
912
936
  "vscode": command_vscode,
913
937
  "update-all": command_update_all,
938
+ "clean-all": command_clean_all,
914
939
  }
915
940
 
916
941
  POST_CONFIG_ACTIONS = {
@@ -919,9 +944,9 @@ POST_CONFIG_ACTIONS = {
919
944
  "upload": command_upload,
920
945
  "logs": command_logs,
921
946
  "run": command_run,
947
+ "clean": command_clean,
922
948
  "clean-mqtt": command_clean_mqtt,
923
949
  "mqtt-fingerprint": command_mqtt_fingerprint,
924
- "clean": command_clean,
925
950
  "idedata": command_idedata,
926
951
  "rename": command_rename,
927
952
  "discover": command_discover,
@@ -965,6 +990,18 @@ def parse_args(argv):
965
990
  help="Add a substitution",
966
991
  metavar=("key", "value"),
967
992
  )
993
+ options_parser.add_argument(
994
+ "--mdns-address-cache",
995
+ help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
996
+ action="append",
997
+ default=[],
998
+ )
999
+ options_parser.add_argument(
1000
+ "--dns-address-cache",
1001
+ help="DNS address cache mapping in format 'hostname=ip1,ip2'",
1002
+ action="append",
1003
+ default=[],
1004
+ )
968
1005
 
969
1006
  parser = argparse.ArgumentParser(
970
1007
  description=f"ESPHome {const.__version__}", parents=[options_parser]
@@ -1122,6 +1159,13 @@ def parse_args(argv):
1122
1159
  "configuration", help="Your YAML configuration file(s).", nargs="+"
1123
1160
  )
1124
1161
 
1162
+ parser_clean_all = subparsers.add_parser(
1163
+ "clean-all", help="Clean all build and platform files."
1164
+ )
1165
+ parser_clean_all.add_argument(
1166
+ "configuration", help="Your YAML configuration directory.", nargs="*"
1167
+ )
1168
+
1125
1169
  parser_dashboard = subparsers.add_parser(
1126
1170
  "dashboard", help="Create a simple web server for a dashboard."
1127
1171
  )
@@ -1168,7 +1212,7 @@ def parse_args(argv):
1168
1212
 
1169
1213
  parser_update = subparsers.add_parser("update-all")
1170
1214
  parser_update.add_argument(
1171
- "configuration", help="Your YAML configuration file directories.", nargs="+"
1215
+ "configuration", help="Your YAML configuration file or directory.", nargs="+"
1172
1216
  )
1173
1217
 
1174
1218
  parser_idedata = subparsers.add_parser("idedata")
@@ -1212,9 +1256,15 @@ def parse_args(argv):
1212
1256
 
1213
1257
 
1214
1258
  def run_esphome(argv):
1259
+ from esphome.address_cache import AddressCache
1260
+
1215
1261
  args = parse_args(argv)
1216
1262
  CORE.dashboard = args.dashboard
1217
1263
 
1264
+ # Create address cache from command-line arguments
1265
+ CORE.address_cache = AddressCache.from_cli_args(
1266
+ args.mdns_address_cache, args.dns_address_cache
1267
+ )
1218
1268
  # Override log level if verbose is set
1219
1269
  if args.verbose:
1220
1270
  args.log_level = "DEBUG"
@@ -1237,14 +1287,20 @@ def run_esphome(argv):
1237
1287
  _LOGGER.info("ESPHome %s", const.__version__)
1238
1288
 
1239
1289
  for conf_path in args.configuration:
1240
- if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
1290
+ conf_path = Path(conf_path)
1291
+ if any(conf_path.name == x for x in SECRETS_FILES):
1241
1292
  _LOGGER.warning("Skipping secrets file %s", conf_path)
1242
1293
  continue
1243
1294
 
1244
1295
  CORE.config_path = conf_path
1245
1296
  CORE.dashboard = args.dashboard
1246
1297
 
1247
- config = read_config(dict(args.substitution) if args.substitution else {})
1298
+ # For logs command, skip updating external components
1299
+ skip_external = args.command == "logs"
1300
+ config = read_config(
1301
+ dict(args.substitution) if args.substitution else {},
1302
+ skip_external_update=skip_external,
1303
+ )
1248
1304
  if config is None:
1249
1305
  return 2
1250
1306
  CORE.config = config
@@ -0,0 +1,142 @@
1
+ """Address cache for DNS and mDNS lookups."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Iterable
10
+
11
+ _LOGGER = logging.getLogger(__name__)
12
+
13
+
14
+ def normalize_hostname(hostname: str) -> str:
15
+ """Normalize hostname for cache lookups.
16
+
17
+ Removes trailing dots and converts to lowercase.
18
+ """
19
+ return hostname.rstrip(".").lower()
20
+
21
+
22
+ class AddressCache:
23
+ """Cache for DNS and mDNS address lookups.
24
+
25
+ This cache stores pre-resolved addresses from command-line arguments
26
+ to avoid slow DNS/mDNS lookups during builds.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ mdns_cache: dict[str, list[str]] | None = None,
32
+ dns_cache: dict[str, list[str]] | None = None,
33
+ ) -> None:
34
+ """Initialize the address cache.
35
+
36
+ Args:
37
+ mdns_cache: Pre-populated mDNS addresses (hostname -> IPs)
38
+ dns_cache: Pre-populated DNS addresses (hostname -> IPs)
39
+ """
40
+ self.mdns_cache = mdns_cache or {}
41
+ self.dns_cache = dns_cache or {}
42
+
43
+ def _get_cached_addresses(
44
+ self, hostname: str, cache: dict[str, list[str]], cache_type: str
45
+ ) -> list[str] | None:
46
+ """Get cached addresses from a specific cache.
47
+
48
+ Args:
49
+ hostname: The hostname to look up
50
+ cache: The cache dictionary to check
51
+ cache_type: Type of cache for logging ("mDNS" or "DNS")
52
+
53
+ Returns:
54
+ List of IP addresses if found in cache, None otherwise
55
+ """
56
+ normalized = normalize_hostname(hostname)
57
+ if addresses := cache.get(normalized):
58
+ _LOGGER.debug("Using %s cache for %s: %s", cache_type, hostname, addresses)
59
+ return addresses
60
+ return None
61
+
62
+ def get_mdns_addresses(self, hostname: str) -> list[str] | None:
63
+ """Get cached mDNS addresses for a hostname.
64
+
65
+ Args:
66
+ hostname: The hostname to look up (should end with .local)
67
+
68
+ Returns:
69
+ List of IP addresses if found in cache, None otherwise
70
+ """
71
+ return self._get_cached_addresses(hostname, self.mdns_cache, "mDNS")
72
+
73
+ def get_dns_addresses(self, hostname: str) -> list[str] | None:
74
+ """Get cached DNS addresses for a hostname.
75
+
76
+ Args:
77
+ hostname: The hostname to look up
78
+
79
+ Returns:
80
+ List of IP addresses if found in cache, None otherwise
81
+ """
82
+ return self._get_cached_addresses(hostname, self.dns_cache, "DNS")
83
+
84
+ def get_addresses(self, hostname: str) -> list[str] | None:
85
+ """Get cached addresses for a hostname.
86
+
87
+ Checks mDNS cache for .local domains, DNS cache otherwise.
88
+
89
+ Args:
90
+ hostname: The hostname to look up
91
+
92
+ Returns:
93
+ List of IP addresses if found in cache, None otherwise
94
+ """
95
+ normalized = normalize_hostname(hostname)
96
+ if normalized.endswith(".local"):
97
+ return self.get_mdns_addresses(hostname)
98
+ return self.get_dns_addresses(hostname)
99
+
100
+ def has_cache(self) -> bool:
101
+ """Check if any cache entries exist."""
102
+ return bool(self.mdns_cache or self.dns_cache)
103
+
104
+ @classmethod
105
+ def from_cli_args(
106
+ cls, mdns_args: Iterable[str], dns_args: Iterable[str]
107
+ ) -> AddressCache:
108
+ """Create cache from command-line arguments.
109
+
110
+ Args:
111
+ mdns_args: List of mDNS cache entries like ['host=ip1,ip2']
112
+ dns_args: List of DNS cache entries like ['host=ip1,ip2']
113
+
114
+ Returns:
115
+ Configured AddressCache instance
116
+ """
117
+ mdns_cache = cls._parse_cache_args(mdns_args)
118
+ dns_cache = cls._parse_cache_args(dns_args)
119
+ return cls(mdns_cache=mdns_cache, dns_cache=dns_cache)
120
+
121
+ @staticmethod
122
+ def _parse_cache_args(cache_args: Iterable[str]) -> dict[str, list[str]]:
123
+ """Parse cache arguments into a dictionary.
124
+
125
+ Args:
126
+ cache_args: List of cache mappings like ['host1=ip1,ip2', 'host2=ip3']
127
+
128
+ Returns:
129
+ Dictionary mapping normalized hostnames to list of IP addresses
130
+ """
131
+ cache: dict[str, list[str]] = {}
132
+ for arg in cache_args:
133
+ if "=" not in arg:
134
+ _LOGGER.warning(
135
+ "Invalid cache format: %s (expected 'hostname=ip1,ip2')", arg
136
+ )
137
+ continue
138
+ hostname, ips = arg.split("=", 1)
139
+ # Normalize hostname for consistent lookups
140
+ normalized = normalize_hostname(hostname)
141
+ cache[normalized] = [ip.strip() for ip in ips.split(",")]
142
+ return cache