esphome 2025.9.3__py3-none-any.whl → 2025.10.0__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 (353) hide show
  1. esphome/__main__.py +103 -37
  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_dsi/models/waveshare.py +27 -36
  167. esphome/components/mipi_spi/display.py +24 -8
  168. esphome/components/mipi_spi/mipi_spi.h +3 -3
  169. esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
  170. esphome/components/mmc5603/mmc5603.cpp +3 -3
  171. esphome/components/modbus/modbus.cpp +27 -13
  172. esphome/components/modbus/modbus.h +5 -3
  173. esphome/components/modbus/modbus_definitions.h +86 -0
  174. esphome/components/modbus_controller/__init__.py +29 -1
  175. esphome/components/modbus_controller/const.py +4 -0
  176. esphome/components/modbus_controller/modbus_controller.cpp +38 -13
  177. esphome/components/modbus_controller/modbus_controller.h +18 -29
  178. esphome/components/mpr121/mpr121.cpp +41 -42
  179. esphome/components/mpr121/mpr121.h +0 -1
  180. esphome/components/nau7802/nau7802.cpp +2 -2
  181. esphome/components/network/__init__.py +7 -3
  182. esphome/components/nextion/display.py +4 -4
  183. esphome/components/nextion/nextion.cpp +8 -8
  184. esphome/components/number/__init__.py +2 -0
  185. esphome/components/number/number_call.cpp +23 -12
  186. esphome/components/number/number_call.h +5 -0
  187. esphome/components/online_image/bmp_image.cpp +2 -1
  188. esphome/components/online_image/jpeg_image.cpp +4 -2
  189. esphome/components/opentherm/opentherm.cpp +5 -5
  190. esphome/components/opentherm/opentherm.h +3 -3
  191. esphome/components/openthread/openthread.cpp +11 -10
  192. esphome/components/openthread/openthread.h +0 -1
  193. esphome/components/ota/ota_backend.h +1 -0
  194. esphome/components/packages/__init__.py +10 -8
  195. esphome/components/packet_transport/packet_transport.cpp +2 -0
  196. esphome/components/pid/pid_controller.cpp +1 -1
  197. esphome/components/prometheus/prometheus_handler.cpp +239 -239
  198. esphome/components/psram/__init__.py +32 -28
  199. esphome/components/qmc5883l/qmc5883l.cpp +15 -0
  200. esphome/components/qmc5883l/qmc5883l.h +3 -0
  201. esphome/components/qmc5883l/sensor.py +31 -12
  202. esphome/components/remote_base/gobox_protocol.cpp +3 -3
  203. esphome/components/remote_receiver/__init__.py +14 -2
  204. esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
  205. esphome/components/remote_receiver/remote_receiver.h +4 -0
  206. esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
  207. esphome/components/remote_transmitter/__init__.py +2 -2
  208. esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
  209. esphome/components/rp2040/__init__.py +11 -11
  210. esphome/components/rtttl/rtttl.cpp +2 -2
  211. esphome/components/scd30/sensor.py +1 -1
  212. esphome/components/script/__init__.py +1 -1
  213. esphome/components/script/script.h +7 -7
  214. esphome/components/select/select.cpp +5 -4
  215. esphome/components/select/select_call.cpp +1 -1
  216. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  217. esphome/components/sensor/__init__.py +2 -0
  218. esphome/components/sha256/__init__.py +22 -0
  219. esphome/components/sha256/sha256.cpp +116 -0
  220. esphome/components/sha256/sha256.h +60 -0
  221. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  222. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  223. esphome/components/speaker/media_player/__init__.py +16 -3
  224. esphome/components/spi/__init__.py +0 -3
  225. esphome/components/split_buffer/__init__.py +5 -0
  226. esphome/components/split_buffer/split_buffer.cpp +133 -0
  227. esphome/components/split_buffer/split_buffer.h +40 -0
  228. esphome/components/sps30/sps30.cpp +14 -10
  229. esphome/components/sps30/sps30.h +2 -0
  230. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  231. esphome/components/st7789v/st7789v.cpp +3 -2
  232. esphome/components/statsd/statsd.cpp +1 -1
  233. esphome/components/substitutions/__init__.py +3 -1
  234. esphome/components/substitutions/jinja.py +13 -3
  235. esphome/components/sx126x/__init__.py +16 -0
  236. esphome/components/sx126x/sx126x.cpp +15 -1
  237. esphome/components/sx126x/sx126x.h +9 -1
  238. esphome/components/sx126x/sx126x_reg.h +2 -0
  239. esphome/components/text_sensor/text_sensor.cpp +16 -0
  240. esphome/components/text_sensor/text_sensor.h +3 -10
  241. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  242. esphome/components/tuya/select/tuya_select.cpp +1 -1
  243. esphome/components/tuya/tuya.cpp +29 -4
  244. esphome/components/uart/__init__.py +37 -27
  245. esphome/components/uart/uart.h +6 -0
  246. esphome/components/uart/uart_component.cpp +8 -0
  247. esphome/components/uart/uart_component.h +28 -0
  248. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  249. esphome/components/uart/uart_component_esp_idf.h +5 -2
  250. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  251. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  252. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  253. esphome/components/usb_host/__init__.py +12 -2
  254. esphome/components/usb_host/usb_host.h +89 -14
  255. esphome/components/usb_host/usb_host_client.cpp +157 -22
  256. esphome/components/usb_host/usb_host_component.cpp +1 -1
  257. esphome/components/usb_uart/__init__.py +0 -1
  258. esphome/components/usb_uart/ch34x.cpp +4 -4
  259. esphome/components/usb_uart/cp210x.cpp +3 -3
  260. esphome/components/usb_uart/usb_uart.cpp +88 -32
  261. esphome/components/usb_uart/usb_uart.h +30 -6
  262. esphome/components/valve/valve.cpp +1 -0
  263. esphome/components/veml7700/veml7700.cpp +7 -6
  264. esphome/components/version/version_text_sensor.cpp +2 -1
  265. esphome/components/voice_assistant/voice_assistant.cpp +3 -2
  266. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  267. esphome/components/web_server/list_entities.cpp +3 -4
  268. esphome/components/web_server/list_entities.h +8 -10
  269. esphome/components/web_server/ota/__init__.py +1 -1
  270. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  271. esphome/components/web_server/web_server.cpp +509 -404
  272. esphome/components/web_server/web_server.h +5 -6
  273. esphome/components/web_server/web_server_v1.cpp +21 -19
  274. esphome/components/web_server_base/__init__.py +5 -2
  275. esphome/components/web_server_base/web_server_base.h +27 -7
  276. esphome/components/web_server_idf/__init__.py +1 -1
  277. esphome/components/web_server_idf/multipart.cpp +2 -2
  278. esphome/components/web_server_idf/multipart.h +2 -2
  279. esphome/components/web_server_idf/utils.cpp +2 -2
  280. esphome/components/web_server_idf/utils.h +2 -2
  281. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  282. esphome/components/web_server_idf/web_server_idf.h +12 -10
  283. esphome/components/wifi/__init__.py +13 -11
  284. esphome/components/wifi/wifi_component.cpp +74 -56
  285. esphome/components/wifi/wifi_component.h +4 -4
  286. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  287. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  288. esphome/components/wireguard/__init__.py +1 -1
  289. esphome/components/wts01/__init__.py +0 -0
  290. esphome/components/wts01/sensor.py +41 -0
  291. esphome/components/wts01/wts01.cpp +91 -0
  292. esphome/components/wts01/wts01.h +27 -0
  293. esphome/components/zephyr/__init__.py +5 -5
  294. esphome/components/zwave_proxy/__init__.py +43 -0
  295. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  296. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  297. esphome/config.py +79 -24
  298. esphome/config_validation.py +13 -15
  299. esphome/const.py +9 -2
  300. esphome/core/__init__.py +33 -22
  301. esphome/core/component.cpp +28 -18
  302. esphome/core/component_iterator.h +2 -1
  303. esphome/core/config.py +15 -15
  304. esphome/core/defines.h +21 -0
  305. esphome/core/entity_helpers.py +9 -6
  306. esphome/core/hash_base.h +56 -0
  307. esphome/core/helpers.cpp +19 -3
  308. esphome/core/helpers.h +26 -0
  309. esphome/core/scheduler.cpp +5 -21
  310. esphome/core/scheduler.h +19 -8
  311. esphome/core/string_ref.h +1 -1
  312. esphome/core/time.cpp +5 -5
  313. esphome/cpp_generator.py +4 -29
  314. esphome/dashboard/const.py +21 -4
  315. esphome/dashboard/core.py +10 -8
  316. esphome/dashboard/dns.py +15 -0
  317. esphome/dashboard/entries.py +15 -21
  318. esphome/dashboard/models.py +76 -0
  319. esphome/dashboard/settings.py +7 -7
  320. esphome/dashboard/status/mdns.py +46 -2
  321. esphome/dashboard/web_server.py +367 -93
  322. esphome/espota2.py +112 -32
  323. esphome/external_files.py +6 -7
  324. esphome/git.py +8 -0
  325. esphome/helpers.py +124 -77
  326. esphome/loader.py +8 -9
  327. esphome/pins.py +2 -2
  328. esphome/platformio_api.py +56 -18
  329. esphome/storage_json.py +26 -21
  330. esphome/types.py +30 -2
  331. esphome/util.py +32 -16
  332. esphome/vscode.py +8 -8
  333. esphome/wizard.py +10 -10
  334. esphome/writer.py +57 -15
  335. esphome/yaml_util.py +37 -31
  336. esphome/zeroconf.py +12 -3
  337. {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/METADATA +12 -12
  338. {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/RECORD +342 -322
  339. esphome/components/event_emitter/__init__.py +0 -5
  340. esphome/components/event_emitter/event_emitter.cpp +0 -14
  341. esphome/components/event_emitter/event_emitter.h +0 -63
  342. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  343. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  344. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  345. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  346. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  347. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  348. esphome/core/string_ref.cpp +0 -12
  349. esphome/dashboard/util/file.py +0 -63
  350. {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/WHEEL +0 -0
  351. {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/entry_points.txt +0 -0
  352. {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/licenses/LICENSE +0 -0
  353. {esphome-2025.9.3.dist-info → esphome-2025.10.0.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
@@ -256,8 +268,10 @@ def has_ip_address() -> bool:
256
268
 
257
269
 
258
270
  def has_resolvable_address() -> bool:
259
- """Check if CORE.address is resolvable (via mDNS or is an IP address)."""
260
- return has_mdns() or has_ip_address()
271
+ """Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
272
+ # Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
273
+ # The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
274
+ return CORE.address is not None
261
275
 
262
276
 
263
277
  def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
@@ -445,7 +459,7 @@ def upload_using_esptool(
445
459
  "detect",
446
460
  ]
447
461
  for img in flash_images:
448
- cmd += [img.offset, img.path]
462
+ cmd += [img.offset, str(img.path)]
449
463
 
450
464
  if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
451
465
  import esptool
@@ -531,7 +545,10 @@ def upload_program(
531
545
 
532
546
  remote_port = int(ota_conf[CONF_PORT])
533
547
  password = ota_conf.get(CONF_PASSWORD, "")
534
- binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
548
+ if getattr(args, "file", None) is not None:
549
+ binary = Path(args.file)
550
+ else:
551
+ binary = CORE.firmware_bin
535
552
 
536
553
  # MQTT address resolution
537
554
  if get_port_type(host) in ("MQTT", "MQTTIP"):
@@ -563,11 +580,12 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
563
580
  if has_api():
564
581
  addresses_to_use: list[str] | None = None
565
582
 
566
- if port_type == "NETWORK" and (has_mdns() or is_ip_address(port)):
583
+ if port_type == "NETWORK":
584
+ # Network addresses (IPs, mDNS names, or regular DNS hostnames) can be used
585
+ # The resolve_ip_address() function in helpers.py handles all types
567
586
  addresses_to_use = devices
568
- elif port_type in ("NETWORK", "MQTT", "MQTTIP") and has_mqtt_ip_lookup():
569
- # Only use MQTT IP lookup if the first condition didn't match
570
- # (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
587
+ elif port_type in ("MQTT", "MQTTIP") and has_mqtt_ip_lookup():
588
+ # Use MQTT IP lookup for MQTT/MQTTIP types
571
589
  addresses_to_use = mqtt_get_ip(
572
590
  config, args.username, args.password, args.client_id
573
591
  )
@@ -598,7 +616,7 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
598
616
  def command_wizard(args: ArgsProtocol) -> int | None:
599
617
  from esphome import wizard
600
618
 
601
- return wizard.wizard(args.configuration)
619
+ return wizard.wizard(Path(args.configuration))
602
620
 
603
621
 
604
622
  def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
@@ -720,6 +738,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
720
738
  return clean_mqtt(config, args)
721
739
 
722
740
 
741
+ def command_clean_all(args: ArgsProtocol) -> int | None:
742
+ try:
743
+ writer.clean_all(args.configuration)
744
+ except OSError as err:
745
+ _LOGGER.error("Error cleaning all files: %s", err)
746
+ return 1
747
+ _LOGGER.info("Done!")
748
+ return 0
749
+
750
+
723
751
  def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
724
752
  from esphome import mqtt
725
753
 
@@ -761,7 +789,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
761
789
  safe_print(f"{half_line}{middle_text}{half_line}")
762
790
 
763
791
  for f in files:
764
- safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
792
+ safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
765
793
  safe_print("-" * twidth)
766
794
  safe_print()
767
795
  if CORE.dashboard:
@@ -773,10 +801,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
773
801
  "esphome", "run", f, "--no-logs", "--device", "OTA"
774
802
  )
775
803
  if rc == 0:
776
- print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
804
+ print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
777
805
  success[f] = True
778
806
  else:
779
- print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
807
+ print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
780
808
  success[f] = False
781
809
 
782
810
  safe_print()
@@ -787,9 +815,9 @@ def command_update_all(args: ArgsProtocol) -> int | None:
787
815
  failed = 0
788
816
  for f in files:
789
817
  if success[f]:
790
- safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
818
+ safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
791
819
  else:
792
- safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
820
+ safe_print(f" - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
793
821
  failed += 1
794
822
  return failed
795
823
 
@@ -811,7 +839,8 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
811
839
 
812
840
 
813
841
  def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
814
- for c in args.name:
842
+ new_name = args.name
843
+ for c in new_name:
815
844
  if c not in ALLOWED_NAME_CHARS:
816
845
  print(
817
846
  color(
@@ -822,8 +851,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
822
851
  )
823
852
  return 1
824
853
  # Load existing yaml file
825
- with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
826
- raw_contents = raw_file.read()
854
+ raw_contents = CORE.config_path.read_text(encoding="utf-8")
827
855
 
828
856
  yaml = yaml_util.load_yaml(CORE.config_path)
829
857
  if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
@@ -838,7 +866,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
838
866
  if match is None:
839
867
  new_raw = re.sub(
840
868
  rf"name:\s+[\"']?{old_name}[\"']?",
841
- f'name: "{args.name}"',
869
+ f'name: "{new_name}"',
842
870
  raw_contents,
843
871
  )
844
872
  else:
@@ -858,29 +886,28 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
858
886
 
859
887
  new_raw = re.sub(
860
888
  rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
861
- f'\\1: "{args.name}"',
889
+ f'\\1: "{new_name}"',
862
890
  raw_contents,
863
891
  flags=re.MULTILINE,
864
892
  )
865
893
 
866
- new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
894
+ new_path: Path = CORE.config_dir / (new_name + ".yaml")
867
895
  print(
868
- f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
896
+ f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
869
897
  )
870
898
  print()
871
899
 
872
- with open(new_path, mode="w", encoding="utf-8") as new_file:
873
- new_file.write(new_raw)
900
+ new_path.write_text(new_raw, encoding="utf-8")
874
901
 
875
- rc = run_external_process("esphome", "config", new_path)
902
+ rc = run_external_process("esphome", "config", str(new_path))
876
903
  if rc != 0:
877
904
  print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
878
- os.remove(new_path)
905
+ new_path.unlink()
879
906
  return 1
880
907
 
881
908
  cli_args = [
882
909
  "run",
883
- new_path,
910
+ str(new_path),
884
911
  "--no-logs",
885
912
  "--device",
886
913
  CORE.address,
@@ -894,11 +921,11 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
894
921
  except KeyboardInterrupt:
895
922
  rc = 1
896
923
  if rc != 0:
897
- os.remove(new_path)
924
+ new_path.unlink()
898
925
  return 1
899
926
 
900
927
  if CORE.config_path != new_path:
901
- os.remove(CORE.config_path)
928
+ CORE.config_path.unlink()
902
929
 
903
930
  print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
904
931
  print()
@@ -911,6 +938,7 @@ PRE_CONFIG_ACTIONS = {
911
938
  "dashboard": command_dashboard,
912
939
  "vscode": command_vscode,
913
940
  "update-all": command_update_all,
941
+ "clean-all": command_clean_all,
914
942
  }
915
943
 
916
944
  POST_CONFIG_ACTIONS = {
@@ -919,9 +947,9 @@ POST_CONFIG_ACTIONS = {
919
947
  "upload": command_upload,
920
948
  "logs": command_logs,
921
949
  "run": command_run,
950
+ "clean": command_clean,
922
951
  "clean-mqtt": command_clean_mqtt,
923
952
  "mqtt-fingerprint": command_mqtt_fingerprint,
924
- "clean": command_clean,
925
953
  "idedata": command_idedata,
926
954
  "rename": command_rename,
927
955
  "discover": command_discover,
@@ -965,6 +993,24 @@ def parse_args(argv):
965
993
  help="Add a substitution",
966
994
  metavar=("key", "value"),
967
995
  )
996
+ options_parser.add_argument(
997
+ "--mdns-address-cache",
998
+ help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
999
+ action="append",
1000
+ default=[],
1001
+ )
1002
+ options_parser.add_argument(
1003
+ "--dns-address-cache",
1004
+ help="DNS address cache mapping in format 'hostname=ip1,ip2'",
1005
+ action="append",
1006
+ default=[],
1007
+ )
1008
+ options_parser.add_argument(
1009
+ "--testing-mode",
1010
+ help="Enable testing mode (disables validation checks for grouped component testing)",
1011
+ action="store_true",
1012
+ default=False,
1013
+ )
968
1014
 
969
1015
  parser = argparse.ArgumentParser(
970
1016
  description=f"ESPHome {const.__version__}", parents=[options_parser]
@@ -1122,6 +1168,13 @@ def parse_args(argv):
1122
1168
  "configuration", help="Your YAML configuration file(s).", nargs="+"
1123
1169
  )
1124
1170
 
1171
+ parser_clean_all = subparsers.add_parser(
1172
+ "clean-all", help="Clean all build and platform files."
1173
+ )
1174
+ parser_clean_all.add_argument(
1175
+ "configuration", help="Your YAML configuration directory.", nargs="*"
1176
+ )
1177
+
1125
1178
  parser_dashboard = subparsers.add_parser(
1126
1179
  "dashboard", help="Create a simple web server for a dashboard."
1127
1180
  )
@@ -1168,7 +1221,7 @@ def parse_args(argv):
1168
1221
 
1169
1222
  parser_update = subparsers.add_parser("update-all")
1170
1223
  parser_update.add_argument(
1171
- "configuration", help="Your YAML configuration file directories.", nargs="+"
1224
+ "configuration", help="Your YAML configuration file or directory.", nargs="+"
1172
1225
  )
1173
1226
 
1174
1227
  parser_idedata = subparsers.add_parser("idedata")
@@ -1212,9 +1265,16 @@ def parse_args(argv):
1212
1265
 
1213
1266
 
1214
1267
  def run_esphome(argv):
1268
+ from esphome.address_cache import AddressCache
1269
+
1215
1270
  args = parse_args(argv)
1216
1271
  CORE.dashboard = args.dashboard
1272
+ CORE.testing_mode = args.testing_mode
1217
1273
 
1274
+ # Create address cache from command-line arguments
1275
+ CORE.address_cache = AddressCache.from_cli_args(
1276
+ args.mdns_address_cache, args.dns_address_cache
1277
+ )
1218
1278
  # Override log level if verbose is set
1219
1279
  if args.verbose:
1220
1280
  args.log_level = "DEBUG"
@@ -1237,14 +1297,20 @@ def run_esphome(argv):
1237
1297
  _LOGGER.info("ESPHome %s", const.__version__)
1238
1298
 
1239
1299
  for conf_path in args.configuration:
1240
- if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
1300
+ conf_path = Path(conf_path)
1301
+ if any(conf_path.name == x for x in SECRETS_FILES):
1241
1302
  _LOGGER.warning("Skipping secrets file %s", conf_path)
1242
1303
  continue
1243
1304
 
1244
1305
  CORE.config_path = conf_path
1245
1306
  CORE.dashboard = args.dashboard
1246
1307
 
1247
- config = read_config(dict(args.substitution) if args.substitution else {})
1308
+ # For logs command, skip updating external components
1309
+ skip_external = args.command == "logs"
1310
+ config = read_config(
1311
+ dict(args.substitution) if args.substitution else {},
1312
+ skip_external_update=skip_external,
1313
+ )
1248
1314
  if config is None:
1249
1315
  return 2
1250
1316
  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