esphome 2025.8.3__py3-none-any.whl → 2025.9.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 (345) hide show
  1. esphome/__main__.py +36 -42
  2. esphome/components/absolute_humidity/absolute_humidity.cpp +3 -5
  3. esphome/components/adc/adc_sensor_esp32.cpp +29 -6
  4. esphome/components/ags10/ags10.cpp +3 -18
  5. esphome/components/ags10/ags10.h +2 -12
  6. esphome/components/aht10/aht10.cpp +3 -3
  7. esphome/components/airthings_ble/__init__.py +2 -2
  8. esphome/components/alarm_control_panel/__init__.py +2 -2
  9. esphome/components/am2315c/am2315c.cpp +1 -17
  10. esphome/components/am2315c/am2315c.h +2 -3
  11. esphome/components/api/__init__.py +2 -2
  12. esphome/components/api/api_connection.cpp +34 -23
  13. esphome/components/api/api_connection.h +20 -39
  14. esphome/components/api/api_frame_helper.cpp +25 -25
  15. esphome/components/api/api_frame_helper.h +3 -3
  16. esphome/components/api/api_frame_helper_noise.cpp +75 -40
  17. esphome/components/api/api_frame_helper_noise.h +3 -7
  18. esphome/components/api/api_frame_helper_plaintext.cpp +17 -4
  19. esphome/components/api/api_frame_helper_plaintext.h +1 -4
  20. esphome/components/api/api_pb2.cpp +20 -2
  21. esphome/components/api/api_pb2.h +146 -141
  22. esphome/components/api/api_pb2_dump.cpp +12 -1
  23. esphome/components/api/proto.cpp +33 -37
  24. esphome/components/async_tcp/__init__.py +2 -2
  25. esphome/components/atm90e26/sensor.py +2 -0
  26. esphome/components/atm90e32/sensor.py +4 -2
  27. esphome/components/audio_adc/__init__.py +2 -2
  28. esphome/components/audio_dac/__init__.py +2 -2
  29. esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +1 -1
  30. esphome/components/bedjet/bedjet_hub.cpp +1 -1
  31. esphome/components/binary_sensor/__init__.py +2 -2
  32. esphome/components/binary_sensor/binary_sensor.cpp +13 -0
  33. esphome/components/binary_sensor/binary_sensor.h +4 -7
  34. esphome/components/bl0940/__init__.py +6 -1
  35. esphome/components/bl0940/bl0940.cpp +178 -41
  36. esphome/components/bl0940/bl0940.h +121 -76
  37. esphome/components/bl0940/button/__init__.py +27 -0
  38. esphome/components/bl0940/button/calibration_reset_button.cpp +20 -0
  39. esphome/components/bl0940/button/calibration_reset_button.h +19 -0
  40. esphome/components/bl0940/number/__init__.py +94 -0
  41. esphome/components/bl0940/number/calibration_number.cpp +29 -0
  42. esphome/components/bl0940/number/calibration_number.h +26 -0
  43. esphome/components/bl0940/sensor.py +151 -2
  44. esphome/components/bl0942/bl0942.cpp +1 -1
  45. esphome/components/ble_client/output/__init__.py +4 -4
  46. esphome/components/bluetooth_proxy/__init__.py +1 -1
  47. esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -1
  48. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +15 -7
  49. esphome/components/bluetooth_proxy/bluetooth_proxy.h +3 -2
  50. esphome/components/button/__init__.py +2 -2
  51. esphome/components/button/button.cpp +13 -0
  52. esphome/components/button/button.h +4 -7
  53. esphome/components/camera/buffer.h +18 -0
  54. esphome/components/camera/buffer_impl.cpp +20 -0
  55. esphome/components/camera/buffer_impl.h +26 -0
  56. esphome/components/camera/camera.h +43 -0
  57. esphome/components/camera/encoder.h +69 -0
  58. esphome/components/camera_encoder/__init__.py +62 -0
  59. esphome/components/camera_encoder/encoder_buffer_impl.cpp +23 -0
  60. esphome/components/camera_encoder/encoder_buffer_impl.h +25 -0
  61. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +82 -0
  62. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +39 -0
  63. esphome/components/captive_portal/__init__.py +2 -2
  64. esphome/components/captive_portal/captive_portal.cpp +35 -12
  65. esphome/components/captive_portal/captive_portal.h +3 -3
  66. esphome/components/ccs811/ccs811.cpp +3 -3
  67. esphome/components/climate/__init__.py +2 -2
  68. esphome/components/climate/climate.cpp +1 -1
  69. esphome/components/cover/__init__.py +5 -5
  70. esphome/components/cover/cover.cpp +1 -1
  71. esphome/components/cover/cover.h +2 -2
  72. esphome/components/dallas_temp/dallas_temp.cpp +2 -2
  73. esphome/components/datetime/__init__.py +2 -2
  74. esphome/components/datetime/date_entity.h +2 -2
  75. esphome/components/datetime/datetime_entity.h +2 -2
  76. esphome/components/datetime/time_entity.h +2 -2
  77. esphome/components/debug/debug_esp32.cpp +1 -1
  78. esphome/components/display/__init__.py +4 -4
  79. esphome/components/duty_time/duty_time_sensor.cpp +1 -1
  80. esphome/components/esp32/__init__.py +0 -5
  81. esphome/components/esp32/gpio.cpp +27 -23
  82. esphome/components/esp32/gpio.h +26 -11
  83. esphome/components/esp32/preferences.cpp +8 -4
  84. esphome/components/esp32_ble/__init__.py +7 -2
  85. esphome/components/esp32_ble_client/ble_client_base.cpp +7 -3
  86. esphome/components/esp32_ble_tracker/__init__.py +2 -2
  87. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +9 -44
  88. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -14
  89. esphome/components/esp8266/__init__.py +2 -2
  90. esphome/components/esp8266/core.cpp +2 -2
  91. esphome/components/esp8266/gpio.py +4 -4
  92. esphome/components/esp8266/preferences.cpp +30 -28
  93. esphome/components/esphome/ota/__init__.py +2 -2
  94. esphome/components/esphome/ota/ota_esphome.cpp +21 -19
  95. esphome/components/esphome/ota/ota_esphome.h +6 -5
  96. esphome/components/ethernet/__init__.py +7 -2
  97. esphome/components/ethernet/ethernet_component.cpp +1 -1
  98. esphome/components/event/__init__.py +2 -2
  99. esphome/components/event/event.h +4 -4
  100. esphome/components/fan/__init__.py +2 -2
  101. esphome/components/fan/fan.cpp +2 -1
  102. esphome/components/gdk101/gdk101.cpp +4 -4
  103. esphome/components/globals/__init__.py +2 -2
  104. esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +19 -18
  105. esphome/components/gpio_expander/cached_gpio.h +36 -16
  106. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +5 -5
  107. esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +1 -1
  108. esphome/components/haier/haier_base.cpp +1 -1
  109. esphome/components/haier/hon_climate.cpp +1 -1
  110. esphome/components/hlw8012/hlw8012.cpp +5 -5
  111. esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +4 -4
  112. esphome/components/host/preferences.h +3 -2
  113. esphome/components/hte501/hte501.cpp +3 -21
  114. esphome/components/hte501/hte501.h +2 -3
  115. esphome/components/http_request/ota/__init__.py +2 -2
  116. esphome/components/i2c/__init__.py +2 -2
  117. esphome/components/i2c/i2c.cpp +13 -9
  118. esphome/components/i2c/i2c_bus.h +36 -6
  119. esphome/components/i2s_audio/__init__.py +8 -2
  120. esphome/components/i2s_audio/media_player/__init__.py +1 -1
  121. esphome/components/i2s_audio/microphone/__init__.py +1 -1
  122. esphome/components/i2s_audio/speaker/__init__.py +1 -1
  123. esphome/components/inkplate/__init__.py +1 -0
  124. esphome/components/inkplate/const.py +105 -0
  125. esphome/components/inkplate/display.py +238 -0
  126. esphome/components/{inkplate6 → inkplate}/inkplate.cpp +156 -74
  127. esphome/components/{inkplate6 → inkplate}/inkplate.h +28 -68
  128. esphome/components/inkplate6/__init__.py +0 -1
  129. esphome/components/inkplate6/display.py +2 -211
  130. esphome/components/integration/integration_sensor.cpp +1 -1
  131. esphome/components/json/__init__.py +2 -2
  132. esphome/components/kmeteriso/kmeteriso.cpp +1 -1
  133. esphome/components/lc709203f/lc709203f.cpp +4 -17
  134. esphome/components/lc709203f/lc709203f.h +2 -3
  135. esphome/components/ld2420/text_sensor/{text_sensor.cpp → ld2420_text_sensor.cpp} +1 -1
  136. esphome/components/ld2450/ld2450.cpp +1 -1
  137. esphome/components/libretiny/preferences.cpp +13 -5
  138. esphome/components/light/__init__.py +2 -2
  139. esphome/components/light/addressable_light_effect.h +7 -0
  140. esphome/components/light/base_light_effects.h +8 -0
  141. esphome/components/light/light_call.cpp +22 -20
  142. esphome/components/light/light_effect.cpp +36 -0
  143. esphome/components/light/light_effect.h +14 -0
  144. esphome/components/light/light_json_schema.cpp +9 -1
  145. esphome/components/light/light_state.cpp +2 -2
  146. esphome/components/light/light_state.h +39 -0
  147. esphome/components/lock/__init__.py +2 -2
  148. esphome/components/lock/lock.h +2 -2
  149. esphome/components/logger/__init__.py +2 -2
  150. esphome/components/logger/logger.cpp +25 -4
  151. esphome/components/logger/logger.h +1 -1
  152. esphome/components/logger/logger_esp32.cpp +16 -8
  153. esphome/components/logger/logger_esp8266.cpp +11 -3
  154. esphome/components/logger/logger_libretiny.cpp +13 -3
  155. esphome/components/logger/logger_rp2040.cpp +14 -3
  156. esphome/components/logger/logger_zephyr.cpp +15 -4
  157. esphome/components/lvgl/defines.py +1 -0
  158. esphome/components/lvgl/hello_world.py +96 -33
  159. esphome/components/lvgl/number/lvgl_number.h +1 -1
  160. esphome/components/lvgl/select/lvgl_select.h +1 -1
  161. esphome/components/lvgl/widgets/__init__.py +0 -1
  162. esphome/components/lvgl/widgets/spinbox.py +20 -11
  163. esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp +1 -1
  164. esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp +1 -1
  165. esphome/components/mapping/__init__.py +13 -5
  166. esphome/components/mapping/mapping.h +69 -0
  167. esphome/components/max17043/max17043.cpp +2 -2
  168. esphome/components/mcp23016/__init__.py +1 -0
  169. esphome/components/mcp23016/mcp23016.cpp +20 -5
  170. esphome/components/mcp23016/mcp23016.h +10 -4
  171. esphome/components/mcp23x08_base/mcp23x08_base.cpp +1 -1
  172. esphome/components/mcp23x17_base/mcp23x17_base.cpp +2 -2
  173. esphome/components/mdns/__init__.py +2 -2
  174. esphome/components/mdns/mdns_component.cpp +145 -54
  175. esphome/components/media_player/__init__.py +2 -2
  176. esphome/components/micro_wake_word/__init__.py +2 -2
  177. esphome/components/microphone/__init__.py +2 -2
  178. esphome/components/mipi/__init__.py +77 -33
  179. esphome/components/mipi_rgb/__init__.py +2 -0
  180. esphome/components/mipi_rgb/display.py +321 -0
  181. esphome/components/mipi_rgb/mipi_rgb.cpp +388 -0
  182. esphome/components/mipi_rgb/mipi_rgb.h +127 -0
  183. esphome/components/mipi_rgb/models/guition.py +24 -0
  184. esphome/components/mipi_rgb/models/lilygo.py +228 -0
  185. esphome/components/mipi_rgb/models/rpi.py +9 -0
  186. esphome/components/mipi_rgb/models/st7701s.py +214 -0
  187. esphome/components/mipi_rgb/models/waveshare.py +64 -0
  188. esphome/components/mipi_spi/models/jc.py +229 -0
  189. esphome/components/mlx90614/mlx90614.cpp +1 -16
  190. esphome/components/mlx90614/mlx90614.h +0 -1
  191. esphome/components/mqtt/__init__.py +2 -2
  192. esphome/components/mqtt/mqtt_sensor.cpp +7 -2
  193. esphome/components/ms5611/ms5611.cpp +7 -6
  194. esphome/components/network/__init__.py +2 -2
  195. esphome/components/nextion/nextion_upload.cpp +4 -1
  196. esphome/components/nrf52/__init__.py +49 -6
  197. esphome/components/nrf52/const.py +1 -0
  198. esphome/components/nrf52/dfu.cpp +51 -0
  199. esphome/components/nrf52/dfu.h +24 -0
  200. esphome/components/ntc/ntc.cpp +1 -1
  201. esphome/components/number/__init__.py +2 -2
  202. esphome/components/number/automation.cpp +1 -1
  203. esphome/components/number/number.cpp +21 -0
  204. esphome/components/number/number.h +4 -13
  205. esphome/components/opentherm/hub.h +6 -6
  206. esphome/components/opentherm/number/{number.cpp → opentherm_number.cpp} +2 -2
  207. esphome/components/opentherm/output/{output.cpp → opentherm_output.cpp} +1 -1
  208. esphome/components/opentherm/switch/{switch.cpp → opentherm_switch.cpp} +1 -1
  209. esphome/components/ota/__init__.py +2 -2
  210. esphome/components/pca6416a/__init__.py +1 -0
  211. esphome/components/pca6416a/pca6416a.cpp +20 -5
  212. esphome/components/pca6416a/pca6416a.h +12 -5
  213. esphome/components/pca9554/__init__.py +2 -1
  214. esphome/components/pca9554/pca9554.cpp +12 -18
  215. esphome/components/pca9554/pca9554.h +10 -9
  216. esphome/components/pcf8574/__init__.py +1 -0
  217. esphome/components/pcf8574/pcf8574.cpp +14 -5
  218. esphome/components/pcf8574/pcf8574.h +13 -6
  219. esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +7 -7
  220. esphome/components/pipsolar/__init__.py +3 -3
  221. esphome/components/pipsolar/output/__init__.py +4 -4
  222. esphome/components/pulse_width/pulse_width.cpp +2 -2
  223. esphome/components/qmp6988/qmp6988.cpp +81 -126
  224. esphome/components/qmp6988/qmp6988.h +31 -37
  225. esphome/components/radon_eye_ble/__init__.py +2 -2
  226. esphome/components/remote_base/__init__.py +6 -8
  227. esphome/components/rotary_encoder/rotary_encoder.cpp +1 -1
  228. esphome/components/rp2040/__init__.py +2 -2
  229. esphome/components/runtime_stats/runtime_stats.cpp +10 -23
  230. esphome/components/runtime_stats/runtime_stats.h +4 -10
  231. esphome/components/safe_mode/__init__.py +2 -2
  232. esphome/components/safe_mode/safe_mode.cpp +33 -31
  233. esphome/components/script/script.cpp +6 -0
  234. esphome/components/script/script.h +19 -5
  235. esphome/components/sdm_meter/sensor.py +3 -1
  236. esphome/components/select/__init__.py +2 -2
  237. esphome/components/select/select.h +2 -2
  238. esphome/components/sen5x/sen5x.cpp +58 -55
  239. esphome/components/sen5x/sen5x.h +21 -15
  240. esphome/components/sen5x/sensor.py +67 -44
  241. esphome/components/sensirion_common/i2c_sensirion.cpp +18 -47
  242. esphome/components/sensirion_common/i2c_sensirion.h +39 -55
  243. esphome/components/sensor/__init__.py +2 -2
  244. esphome/components/sensor/automation.h +1 -1
  245. esphome/components/sensor/sensor.cpp +34 -6
  246. esphome/components/sensor/sensor.h +4 -21
  247. esphome/components/sgp30/sgp30.cpp +34 -35
  248. esphome/components/sgp30/sgp30.h +11 -10
  249. esphome/components/sgp4x/sgp4x.cpp +2 -2
  250. esphome/components/shelly_dimmer/light.py +7 -7
  251. esphome/components/sht4x/sht4x.cpp +1 -1
  252. esphome/components/sntp/sntp_component.cpp +36 -9
  253. esphome/components/sntp/sntp_component.h +7 -0
  254. esphome/components/sound_level/sound_level.cpp +1 -1
  255. esphome/components/speaker/__init__.py +2 -2
  256. esphome/components/speaker/media_player/__init__.py +2 -2
  257. esphome/components/speaker/media_player/speaker_media_player.cpp +1 -1
  258. esphome/components/spi/__init__.py +2 -2
  259. esphome/components/sprinkler/sprinkler.cpp +1 -1
  260. esphome/components/sps30/sps30.cpp +18 -23
  261. esphome/components/sps30/sps30.h +3 -3
  262. esphome/components/status_led/__init__.py +2 -2
  263. esphome/components/stepper/__init__.py +2 -2
  264. esphome/components/switch/__init__.py +2 -2
  265. esphome/components/switch/switch.cpp +5 -5
  266. esphome/components/sx1509/__init__.py +1 -1
  267. esphome/components/sx1509/sx1509.cpp +12 -7
  268. esphome/components/sx1509/sx1509.h +11 -4
  269. esphome/components/tca9555/tca9555.cpp +5 -5
  270. esphome/components/tee501/tee501.cpp +2 -21
  271. esphome/components/tee501/tee501.h +2 -4
  272. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +1 -1
  273. esphome/components/template/datetime/template_date.cpp +1 -1
  274. esphome/components/template/datetime/template_datetime.cpp +2 -2
  275. esphome/components/template/datetime/template_time.cpp +1 -1
  276. esphome/components/template/number/template_number.cpp +1 -1
  277. esphome/components/template/select/template_select.cpp +1 -1
  278. esphome/components/template/text/template_text.cpp +1 -1
  279. esphome/components/text/__init__.py +2 -2
  280. esphome/components/text/text.h +2 -2
  281. esphome/components/text_sensor/__init__.py +2 -2
  282. esphome/components/text_sensor/text_sensor.h +4 -4
  283. esphome/components/thermostat/climate.py +11 -7
  284. esphome/components/thermostat/thermostat_climate.cpp +237 -206
  285. esphome/components/thermostat/thermostat_climate.h +52 -41
  286. esphome/components/time/__init__.py +2 -2
  287. esphome/components/tmp1075/tmp1075.cpp +1 -1
  288. esphome/components/total_daily_energy/total_daily_energy.cpp +1 -1
  289. esphome/components/touchscreen/__init__.py +2 -2
  290. esphome/components/tuya/number/tuya_number.cpp +1 -1
  291. esphome/components/udp/udp_component.cpp +3 -3
  292. esphome/components/ufire_ec/ufire_ec.cpp +4 -4
  293. esphome/components/ufire_ise/ufire_ise.cpp +4 -4
  294. esphome/components/update/__init__.py +2 -2
  295. esphome/components/usb_uart/usb_uart.cpp +1 -1
  296. esphome/components/valve/__init__.py +5 -5
  297. esphome/components/valve/valve.cpp +1 -1
  298. esphome/components/valve/valve.h +2 -2
  299. esphome/components/wake_on_lan/wake_on_lan.cpp +2 -2
  300. esphome/components/waveshare_epaper/waveshare_213v3.cpp +1 -1
  301. esphome/components/web_server/__init__.py +2 -2
  302. esphome/components/web_server/ota/__init__.py +2 -2
  303. esphome/components/web_server/ota/ota_web_server.cpp +11 -0
  304. esphome/components/web_server/web_server.cpp +58 -12
  305. esphome/components/web_server_base/__init__.py +2 -2
  306. esphome/components/wifi/__init__.py +5 -5
  307. esphome/components/wifi/wifi_component.cpp +3 -3
  308. esphome/components/wifi/wifi_component_esp_idf.cpp +2 -0
  309. esphome/config_validation.py +2 -2
  310. esphome/const.py +2 -1
  311. esphome/core/__init__.py +1 -0
  312. esphome/core/application.cpp +89 -51
  313. esphome/core/application.h +1 -0
  314. esphome/core/component.cpp +41 -19
  315. esphome/core/component.h +17 -13
  316. esphome/core/config.py +7 -7
  317. esphome/core/defines.h +4 -0
  318. esphome/core/entity_base.cpp +22 -8
  319. esphome/core/entity_base.h +43 -0
  320. esphome/core/helpers.cpp +26 -13
  321. esphome/core/helpers.h +4 -3
  322. esphome/core/ring_buffer.cpp +6 -2
  323. esphome/core/ring_buffer.h +2 -1
  324. esphome/core/scheduler.cpp +175 -94
  325. esphome/core/scheduler.h +66 -35
  326. esphome/core/time.cpp +6 -20
  327. esphome/coroutine.py +80 -3
  328. esphome/cpp_generator.py +13 -0
  329. esphome/cpp_helpers.py +2 -2
  330. esphome/dashboard/web_server.py +67 -10
  331. esphome/espota2.py +13 -6
  332. esphome/helpers.py +68 -83
  333. esphome/resolver.py +67 -0
  334. esphome/util.py +9 -6
  335. esphome/wizard.py +39 -26
  336. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/METADATA +9 -9
  337. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/RECORD +345 -314
  338. /esphome/components/ld2420/text_sensor/{text_sensor.h → ld2420_text_sensor.h} +0 -0
  339. /esphome/components/opentherm/number/{number.h → opentherm_number.h} +0 -0
  340. /esphome/components/opentherm/output/{output.h → opentherm_output.h} +0 -0
  341. /esphome/components/opentherm/switch/{switch.h → opentherm_switch.h} +0 -0
  342. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/WHEEL +0 -0
  343. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/entry_points.txt +0 -0
  344. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/licenses/LICENSE +0 -0
  345. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/top_level.txt +0 -0
esphome/helpers.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import codecs
2
4
  from contextlib import suppress
3
5
  import ipaddress
@@ -11,6 +13,18 @@ from urllib.parse import urlparse
11
13
 
12
14
  from esphome.const import __version__ as ESPHOME_VERSION
13
15
 
16
+ # Type aliases for socket address information
17
+ AddrInfo = tuple[
18
+ int, # family (AF_INET, AF_INET6, etc.)
19
+ int, # type (SOCK_STREAM, SOCK_DGRAM, etc.)
20
+ int, # proto (IPPROTO_TCP, etc.)
21
+ str, # canonname
22
+ tuple[str, int] | tuple[str, int, int, int], # sockaddr (IPv4 or IPv6)
23
+ ]
24
+ IPv4SockAddr = tuple[str, int] # (host, port)
25
+ IPv6SockAddr = tuple[str, int, int, int] # (host, port, flowinfo, scope_id)
26
+ SockAddr = IPv4SockAddr | IPv6SockAddr
27
+
14
28
  _LOGGER = logging.getLogger(__name__)
15
29
 
16
30
  IS_MACOS = platform.system() == "Darwin"
@@ -147,32 +161,7 @@ def is_ip_address(host):
147
161
  return False
148
162
 
149
163
 
150
- def _resolve_with_zeroconf(host):
151
- from esphome.core import EsphomeError
152
- from esphome.zeroconf import EsphomeZeroconf
153
-
154
- try:
155
- zc = EsphomeZeroconf()
156
- except Exception as err:
157
- raise EsphomeError(
158
- "Cannot start mDNS sockets, is this a docker container without "
159
- "host network mode?"
160
- ) from err
161
- try:
162
- info = zc.resolve_host(f"{host}.")
163
- except Exception as err:
164
- raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err
165
- finally:
166
- zc.close()
167
- if info is None:
168
- raise EsphomeError(
169
- "Error resolving address with mDNS: Did not respond. "
170
- "Maybe the device is offline."
171
- )
172
- return info
173
-
174
-
175
- def addr_preference_(res):
164
+ def addr_preference_(res: AddrInfo) -> int:
176
165
  # Trivial alternative to RFC6724 sorting. Put sane IPv6 first, then
177
166
  # Legacy IP, then IPv6 link-local addresses without an actual link.
178
167
  sa = res[4]
@@ -184,66 +173,70 @@ def addr_preference_(res):
184
173
  return 1
185
174
 
186
175
 
187
- def resolve_ip_address(host, port):
176
+ def resolve_ip_address(host: str | list[str], port: int) -> list[AddrInfo]:
188
177
  import socket
189
178
 
190
- from esphome.core import EsphomeError
191
-
192
179
  # There are five cases here. The host argument could be one of:
193
180
  # • a *list* of IP addresses discovered by MQTT,
194
181
  # • a single IP address specified by the user,
195
182
  # • a .local hostname to be resolved by mDNS,
196
183
  # • a normal hostname to be resolved in DNS, or
197
184
  # • A URL from which we should extract the hostname.
198
- #
199
- # In each of the first three cases, we end up with IP addresses in
200
- # string form which need to be converted to a 5-tuple to be used
201
- # for the socket connection attempt. The easiest way to construct
202
- # those is to pass the IP address string to getaddrinfo(). Which,
203
- # coincidentally, is how we do hostname lookups in the other cases
204
- # too. So first build a list which contains either IP addresses or
205
- # a single hostname, then call getaddrinfo() on each element of
206
- # that list.
207
-
208
- errs = []
185
+
186
+ hosts: list[str]
209
187
  if isinstance(host, list):
210
- addr_list = host
211
- elif is_ip_address(host):
212
- addr_list = [host]
188
+ hosts = host
213
189
  else:
214
- url = urlparse(host)
215
- if url.scheme != "":
216
- host = url.hostname
217
-
218
- addr_list = []
219
- if host.endswith(".local"):
190
+ if not is_ip_address(host):
191
+ url = urlparse(host)
192
+ if url.scheme != "":
193
+ host = url.hostname
194
+ hosts = [host]
195
+
196
+ res: list[AddrInfo] = []
197
+ if all(is_ip_address(h) for h in hosts):
198
+ # Fast path: all are IP addresses, use socket.getaddrinfo with AI_NUMERICHOST
199
+ for addr in hosts:
220
200
  try:
221
- _LOGGER.info("Resolving IP address of %s in mDNS", host)
222
- addr_list = _resolve_with_zeroconf(host)
223
- except EsphomeError as err:
224
- errs.append(str(err))
225
-
226
- # If not mDNS, or if mDNS failed, use normal DNS
227
- if not addr_list:
228
- addr_list = [host]
229
-
230
- # Now we have a list containing either IP addresses or a hostname
231
- res = []
232
- for addr in addr_list:
233
- if not is_ip_address(addr):
234
- _LOGGER.info("Resolving IP address of %s", host)
235
- try:
236
- r = socket.getaddrinfo(addr, port, proto=socket.IPPROTO_TCP)
237
- except OSError as err:
238
- errs.append(str(err))
239
- raise EsphomeError(
240
- f"Error resolving IP address: {', '.join(errs)}"
241
- ) from err
242
-
243
- res = res + r
201
+ res += socket.getaddrinfo(
202
+ addr, port, proto=socket.IPPROTO_TCP, flags=socket.AI_NUMERICHOST
203
+ )
204
+ except OSError:
205
+ _LOGGER.debug("Failed to parse IP address '%s'", addr)
206
+ # Sort by preference
207
+ res.sort(key=addr_preference_)
208
+ return res
209
+
210
+ from esphome.resolver import AsyncResolver
211
+
212
+ resolver = AsyncResolver(hosts, port)
213
+ addr_infos = resolver.resolve()
214
+ # Convert aioesphomeapi AddrInfo to our format
215
+ for addr_info in addr_infos:
216
+ sockaddr = addr_info.sockaddr
217
+ if addr_info.family == socket.AF_INET6:
218
+ # IPv6
219
+ sockaddr_tuple = (
220
+ sockaddr.address,
221
+ sockaddr.port,
222
+ sockaddr.flowinfo,
223
+ sockaddr.scope_id,
224
+ )
225
+ else:
226
+ # IPv4
227
+ sockaddr_tuple = (sockaddr.address, sockaddr.port)
228
+
229
+ res.append(
230
+ (
231
+ addr_info.family,
232
+ addr_info.type,
233
+ addr_info.proto,
234
+ "", # canonname
235
+ sockaddr_tuple,
236
+ )
237
+ )
244
238
 
245
- # Zeroconf tends to give us link-local IPv6 addresses without specifying
246
- # the link. Put those last in the list to be attempted.
239
+ # Sort by preference
247
240
  res.sort(key=addr_preference_)
248
241
  return res
249
242
 
@@ -262,15 +255,7 @@ def sort_ip_addresses(address_list: list[str]) -> list[str]:
262
255
 
263
256
  # First "resolve" all the IP addresses to getaddrinfo() tuples of the form
264
257
  # (family, type, proto, canonname, sockaddr)
265
- res: list[
266
- tuple[
267
- int,
268
- int,
269
- int,
270
- str | None,
271
- tuple[str, int] | tuple[str, int, int, int],
272
- ]
273
- ] = []
258
+ res: list[AddrInfo] = []
274
259
  for addr in address_list:
275
260
  # This should always work as these are supposed to be IP addresses
276
261
  try:
esphome/resolver.py ADDED
@@ -0,0 +1,67 @@
1
+ """DNS resolver for ESPHome using aioesphomeapi."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import threading
7
+
8
+ from aioesphomeapi.core import ResolveAPIError, ResolveTimeoutAPIError
9
+ import aioesphomeapi.host_resolver as hr
10
+
11
+ from esphome.core import EsphomeError
12
+
13
+ RESOLVE_TIMEOUT = 10.0 # seconds
14
+
15
+
16
+ class AsyncResolver(threading.Thread):
17
+ """Resolver using aioesphomeapi that runs in a thread for faster results.
18
+
19
+ This resolver uses aioesphomeapi's async_resolve_host to handle DNS resolution,
20
+ including proper .local domain fallback. Running in a thread allows us to get
21
+ the result immediately without waiting for asyncio.run() to complete its
22
+ cleanup cycle, which can take significant time.
23
+ """
24
+
25
+ def __init__(self, hosts: list[str], port: int) -> None:
26
+ """Initialize the resolver."""
27
+ super().__init__(daemon=True)
28
+ self.hosts = hosts
29
+ self.port = port
30
+ self.result: list[hr.AddrInfo] | None = None
31
+ self.exception: Exception | None = None
32
+ self.event = threading.Event()
33
+
34
+ async def _resolve(self) -> None:
35
+ """Resolve hostnames to IP addresses."""
36
+ try:
37
+ self.result = await hr.async_resolve_host(
38
+ self.hosts, self.port, timeout=RESOLVE_TIMEOUT
39
+ )
40
+ except Exception as e: # pylint: disable=broad-except
41
+ # We need to catch all exceptions to ensure the event is set
42
+ # Otherwise the thread could hang forever
43
+ self.exception = e
44
+ finally:
45
+ self.event.set()
46
+
47
+ def run(self) -> None:
48
+ """Run the DNS resolution."""
49
+ asyncio.run(self._resolve())
50
+
51
+ def resolve(self) -> list[hr.AddrInfo]:
52
+ """Start the thread and wait for the result."""
53
+ self.start()
54
+
55
+ if not self.event.wait(
56
+ timeout=RESOLVE_TIMEOUT + 1.0
57
+ ): # Give it 1 second more than the resolver timeout
58
+ raise EsphomeError("Timeout resolving IP address")
59
+
60
+ if exc := self.exception:
61
+ if isinstance(exc, ResolveTimeoutAPIError):
62
+ raise EsphomeError(f"Timeout resolving IP address: {exc}") from exc
63
+ if isinstance(exc, ResolveAPIError):
64
+ raise EsphomeError(f"Error resolving IP address: {exc}") from exc
65
+ raise exc
66
+
67
+ return self.result
esphome/util.py CHANGED
@@ -272,12 +272,15 @@ class OrderedDict(collections.OrderedDict):
272
272
  return dict(self).__repr__()
273
273
 
274
274
 
275
- def list_yaml_files(folders: list[str]) -> list[str]:
276
- files = filter_yaml_files(
277
- [os.path.join(folder, p) for folder in folders for p in os.listdir(folder)]
278
- )
279
- files.sort()
280
- return files
275
+ def list_yaml_files(configs: list[str]) -> list[str]:
276
+ files: list[str] = []
277
+ for config in configs:
278
+ if os.path.isfile(config):
279
+ files.append(config)
280
+ else:
281
+ files.extend(os.path.join(config, p) for p in os.listdir(config))
282
+ files = filter_yaml_files(files)
283
+ return sorted(files)
281
284
 
282
285
 
283
286
  def filter_yaml_files(files: list[str]) -> list[str]:
esphome/wizard.py CHANGED
@@ -189,32 +189,45 @@ def wizard_write(path, **kwargs):
189
189
  from esphome.components.rtl87xx import boards as rtl87xx_boards
190
190
 
191
191
  name = kwargs["name"]
192
- board = kwargs["board"]
193
-
194
- for key in ("ssid", "psk", "password", "ota_password"):
195
- if key in kwargs:
196
- kwargs[key] = sanitize_double_quotes(kwargs[key])
197
-
198
- if "platform" not in kwargs:
199
- if board in esp8266_boards.BOARDS:
200
- platform = "ESP8266"
201
- elif board in esp32_boards.BOARDS:
202
- platform = "ESP32"
203
- elif board in rp2040_boards.BOARDS:
204
- platform = "RP2040"
205
- elif board in bk72xx_boards.BOARDS:
206
- platform = "BK72XX"
207
- elif board in ln882x_boards.BOARDS:
208
- platform = "LN882X"
209
- elif board in rtl87xx_boards.BOARDS:
210
- platform = "RTL87XX"
211
- else:
212
- safe_print(color(AnsiFore.RED, f'The board "{board}" is unknown.'))
213
- return False
214
- kwargs["platform"] = platform
215
- hardware = kwargs["platform"]
216
-
217
- write_file(path, wizard_file(**kwargs))
192
+ if kwargs["type"] == "empty":
193
+ file_text = ""
194
+ # Will be updated later after editing the file
195
+ hardware = "UNKNOWN"
196
+ elif kwargs["type"] == "upload":
197
+ file_text = kwargs["file_text"]
198
+ hardware = "UNKNOWN"
199
+ else: # "basic"
200
+ board = kwargs["board"]
201
+
202
+ for key in ("ssid", "psk", "password", "ota_password"):
203
+ if key in kwargs:
204
+ kwargs[key] = sanitize_double_quotes(kwargs[key])
205
+ if "platform" not in kwargs:
206
+ if board in esp8266_boards.BOARDS:
207
+ platform = "ESP8266"
208
+ elif board in esp32_boards.BOARDS:
209
+ platform = "ESP32"
210
+ elif board in rp2040_boards.BOARDS:
211
+ platform = "RP2040"
212
+ elif board in bk72xx_boards.BOARDS:
213
+ platform = "BK72XX"
214
+ elif board in ln882x_boards.BOARDS:
215
+ platform = "LN882X"
216
+ elif board in rtl87xx_boards.BOARDS:
217
+ platform = "RTL87XX"
218
+ else:
219
+ safe_print(color(AnsiFore.RED, f'The board "{board}" is unknown.'))
220
+ return False
221
+ kwargs["platform"] = platform
222
+ hardware = kwargs["platform"]
223
+ file_text = wizard_file(**kwargs)
224
+
225
+ # Check if file already exists to prevent overwriting
226
+ if os.path.exists(path) and os.path.isfile(path):
227
+ safe_print(color(AnsiFore.RED, f'The file "{path}" already exists.'))
228
+ return False
229
+
230
+ write_file(path, file_text)
218
231
  storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
219
232
  storage_path = ext_storage_path(os.path.basename(path))
220
233
  storage.save(storage_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esphome
3
- Version: 2025.8.3
3
+ Version: 2025.9.0b1
4
4
  Summary: ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems.
5
5
  Author-email: The ESPHome Authors <esphome@openhomefoundation.org>
6
6
  License: MIT
@@ -36,11 +36,11 @@ Requires-Dist: pyserial==3.5
36
36
  Requires-Dist: platformio==6.1.18
37
37
  Requires-Dist: esptool==5.0.2
38
38
  Requires-Dist: click==8.1.7
39
- Requires-Dist: esphome-dashboard==20250814.0
40
- Requires-Dist: aioesphomeapi==39.0.0
41
- Requires-Dist: zeroconf==0.147.0
39
+ Requires-Dist: esphome-dashboard==20250904.0
40
+ Requires-Dist: aioesphomeapi==40.1.0
41
+ Requires-Dist: zeroconf==0.147.2
42
42
  Requires-Dist: puremagic==1.30
43
- Requires-Dist: ruamel.yaml==0.18.14
43
+ Requires-Dist: ruamel.yaml==0.18.15
44
44
  Requires-Dist: esphome-glyphsets==0.2.0
45
45
  Requires-Dist: pillow==10.4.0
46
46
  Requires-Dist: cairosvg==2.8.2
@@ -56,12 +56,12 @@ Requires-Dist: yamllint==1.37.1; extra == "dev"
56
56
  Provides-Extra: test
57
57
  Requires-Dist: pylint==3.3.8; extra == "test"
58
58
  Requires-Dist: flake8==7.3.0; extra == "test"
59
- Requires-Dist: ruff==0.12.8; extra == "test"
59
+ Requires-Dist: ruff==0.12.12; extra == "test"
60
60
  Requires-Dist: pyupgrade==3.20.0; extra == "test"
61
61
  Requires-Dist: pre-commit; extra == "test"
62
- Requires-Dist: pytest==8.4.1; extra == "test"
63
- Requires-Dist: pytest-cov==6.2.1; extra == "test"
64
- Requires-Dist: pytest-mock==3.14.1; extra == "test"
62
+ Requires-Dist: pytest==8.4.2; extra == "test"
63
+ Requires-Dist: pytest-cov==7.0.0; extra == "test"
64
+ Requires-Dist: pytest-mock==3.15.0; extra == "test"
65
65
  Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
66
66
  Requires-Dist: pytest-xdist==3.8.0; extra == "test"
67
67
  Requires-Dist: asyncmock==0.4.2; extra == "test"