esphome 2025.9.3__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 (343) 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 +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 +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/socket/lwip_raw_tcp_impl.cpp +34 -6
  215. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  216. esphome/components/spi/__init__.py +0 -3
  217. esphome/components/split_buffer/__init__.py +5 -0
  218. esphome/components/split_buffer/split_buffer.cpp +133 -0
  219. esphome/components/split_buffer/split_buffer.h +40 -0
  220. esphome/components/sps30/sps30.cpp +14 -10
  221. esphome/components/sps30/sps30.h +2 -0
  222. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  223. esphome/components/st7789v/st7789v.cpp +3 -2
  224. esphome/components/statsd/statsd.cpp +1 -1
  225. esphome/components/substitutions/__init__.py +3 -1
  226. esphome/components/substitutions/jinja.py +13 -3
  227. esphome/components/sx126x/__init__.py +16 -0
  228. esphome/components/sx126x/sx126x.cpp +15 -1
  229. esphome/components/sx126x/sx126x.h +9 -1
  230. esphome/components/sx126x/sx126x_reg.h +2 -0
  231. esphome/components/text_sensor/text_sensor.cpp +16 -0
  232. esphome/components/text_sensor/text_sensor.h +3 -10
  233. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  234. esphome/components/tuya/select/tuya_select.cpp +1 -1
  235. esphome/components/tuya/tuya.cpp +29 -4
  236. esphome/components/uart/__init__.py +36 -26
  237. esphome/components/uart/uart.h +6 -0
  238. esphome/components/uart/uart_component.cpp +8 -0
  239. esphome/components/uart/uart_component.h +28 -0
  240. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  241. esphome/components/uart/uart_component_esp_idf.h +5 -2
  242. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  243. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  244. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  245. esphome/components/usb_host/__init__.py +2 -1
  246. esphome/components/usb_host/usb_host.h +82 -13
  247. esphome/components/usb_host/usb_host_client.cpp +180 -24
  248. esphome/components/usb_host/usb_host_component.cpp +1 -1
  249. esphome/components/usb_uart/__init__.py +0 -1
  250. esphome/components/usb_uart/ch34x.cpp +4 -4
  251. esphome/components/usb_uart/cp210x.cpp +3 -3
  252. esphome/components/usb_uart/usb_uart.cpp +88 -32
  253. esphome/components/usb_uart/usb_uart.h +30 -6
  254. esphome/components/valve/valve.cpp +1 -0
  255. esphome/components/veml7700/veml7700.cpp +7 -6
  256. esphome/components/version/version_text_sensor.cpp +2 -1
  257. esphome/components/voice_assistant/voice_assistant.cpp +3 -2
  258. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  259. esphome/components/web_server/list_entities.cpp +3 -4
  260. esphome/components/web_server/list_entities.h +8 -10
  261. esphome/components/web_server/ota/__init__.py +1 -1
  262. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  263. esphome/components/web_server/web_server.cpp +509 -404
  264. esphome/components/web_server/web_server.h +5 -6
  265. esphome/components/web_server/web_server_v1.cpp +21 -19
  266. esphome/components/web_server_base/__init__.py +5 -2
  267. esphome/components/web_server_base/web_server_base.h +27 -7
  268. esphome/components/web_server_idf/__init__.py +1 -1
  269. esphome/components/web_server_idf/multipart.cpp +2 -2
  270. esphome/components/web_server_idf/multipart.h +2 -2
  271. esphome/components/web_server_idf/utils.cpp +2 -2
  272. esphome/components/web_server_idf/utils.h +2 -2
  273. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  274. esphome/components/web_server_idf/web_server_idf.h +12 -10
  275. esphome/components/wifi/__init__.py +13 -11
  276. esphome/components/wifi/wifi_component.cpp +73 -56
  277. esphome/components/wifi/wifi_component.h +4 -4
  278. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  279. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  280. esphome/components/wireguard/__init__.py +1 -1
  281. esphome/components/wts01/__init__.py +0 -0
  282. esphome/components/wts01/sensor.py +41 -0
  283. esphome/components/wts01/wts01.cpp +91 -0
  284. esphome/components/wts01/wts01.h +27 -0
  285. esphome/components/zephyr/__init__.py +5 -5
  286. esphome/components/zwave_proxy/__init__.py +43 -0
  287. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  288. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  289. esphome/config.py +79 -24
  290. esphome/config_validation.py +13 -15
  291. esphome/const.py +9 -2
  292. esphome/core/__init__.py +31 -22
  293. esphome/core/component.cpp +28 -18
  294. esphome/core/component_iterator.h +2 -1
  295. esphome/core/config.py +15 -15
  296. esphome/core/defines.h +19 -0
  297. esphome/core/hash_base.h +56 -0
  298. esphome/core/helpers.cpp +19 -3
  299. esphome/core/helpers.h +26 -0
  300. esphome/core/scheduler.cpp +5 -21
  301. esphome/core/scheduler.h +19 -8
  302. esphome/core/string_ref.h +1 -1
  303. esphome/core/time.cpp +5 -5
  304. esphome/cpp_generator.py +4 -29
  305. esphome/dashboard/const.py +21 -4
  306. esphome/dashboard/core.py +10 -8
  307. esphome/dashboard/dns.py +15 -0
  308. esphome/dashboard/entries.py +15 -21
  309. esphome/dashboard/models.py +76 -0
  310. esphome/dashboard/settings.py +7 -7
  311. esphome/dashboard/status/mdns.py +46 -2
  312. esphome/dashboard/web_server.py +367 -93
  313. esphome/espota2.py +111 -31
  314. esphome/external_files.py +6 -7
  315. esphome/git.py +8 -0
  316. esphome/helpers.py +124 -77
  317. esphome/loader.py +8 -9
  318. esphome/platformio_api.py +25 -18
  319. esphome/storage_json.py +26 -21
  320. esphome/types.py +30 -2
  321. esphome/util.py +32 -16
  322. esphome/vscode.py +8 -8
  323. esphome/wizard.py +10 -10
  324. esphome/writer.py +50 -15
  325. esphome/yaml_util.py +37 -31
  326. esphome/zeroconf.py +12 -3
  327. {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/METADATA +11 -11
  328. {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/RECORD +332 -312
  329. esphome/components/event_emitter/__init__.py +0 -5
  330. esphome/components/event_emitter/event_emitter.cpp +0 -14
  331. esphome/components/event_emitter/event_emitter.h +0 -63
  332. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  333. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  334. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  335. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  336. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  337. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  338. esphome/core/string_ref.cpp +0 -12
  339. esphome/dashboard/util/file.py +0 -63
  340. {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/WHEEL +0 -0
  341. {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/entry_points.txt +0 -0
  342. {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/licenses/LICENSE +0 -0
  343. {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/top_level.txt +0 -0
esphome/loader.py CHANGED
@@ -82,11 +82,10 @@ class ComponentManifest:
82
82
  return getattr(self.module, "CONFLICTS_WITH", [])
83
83
 
84
84
  @property
85
- def auto_load(self) -> list[str]:
86
- al = getattr(self.module, "AUTO_LOAD", [])
87
- if callable(al):
88
- return al()
89
- return al
85
+ def auto_load(
86
+ self,
87
+ ) -> list[str] | Callable[[], list[str]] | Callable[[ConfigType], list[str]]:
88
+ return getattr(self.module, "AUTO_LOAD", [])
90
89
 
91
90
  @property
92
91
  def codeowners(self) -> list[str]:
@@ -192,7 +191,7 @@ def install_custom_components_meta_finder():
192
191
  install_meta_finder(custom_components_dir)
193
192
 
194
193
 
195
- def _lookup_module(domain, exception):
194
+ def _lookup_module(domain: str, exception: bool) -> ComponentManifest | None:
196
195
  if domain in _COMPONENT_CACHE:
197
196
  return _COMPONENT_CACHE[domain]
198
197
 
@@ -219,16 +218,16 @@ def _lookup_module(domain, exception):
219
218
  return manif
220
219
 
221
220
 
222
- def get_component(domain, exception=False):
221
+ def get_component(domain: str, exception: bool = False) -> ComponentManifest | None:
223
222
  assert "." not in domain
224
223
  return _lookup_module(domain, exception)
225
224
 
226
225
 
227
- def get_platform(domain, platform):
226
+ def get_platform(domain: str, platform: str) -> ComponentManifest | None:
228
227
  full = f"{platform}.{domain}"
229
228
  return _lookup_module(full, False)
230
229
 
231
230
 
232
- _COMPONENT_CACHE = {}
231
+ _COMPONENT_CACHE: dict[str, ComponentManifest] = {}
233
232
  CORE_COMPONENTS_PATH = (Path(__file__).parent / "components").resolve()
234
233
  _COMPONENT_CACHE["esphome"] = ComponentManifest(esphome.core.config)
esphome/platformio_api.py CHANGED
@@ -18,23 +18,25 @@ def patch_structhash():
18
18
  # removed/added. This might have unintended consequences, but this improves compile
19
19
  # times greatly when adding/removing components and a simple clean build solves
20
20
  # all issues
21
- from os import makedirs
22
- from os.path import getmtime, isdir, join
23
-
24
21
  from platformio.run import cli, helpers
25
22
 
26
23
  def patched_clean_build_dir(build_dir, *args):
27
24
  from platformio import fs
28
25
  from platformio.project.helpers import get_project_dir
29
26
 
30
- platformio_ini = join(get_project_dir(), "platformio.ini")
27
+ platformio_ini = Path(get_project_dir()) / "platformio.ini"
28
+
29
+ build_dir = Path(build_dir)
31
30
 
32
31
  # if project's config is modified
33
- if isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir):
32
+ if (
33
+ build_dir.is_dir()
34
+ and platformio_ini.stat().st_mtime > build_dir.stat().st_mtime
35
+ ):
34
36
  fs.rmtree(build_dir)
35
37
 
36
- if not isdir(build_dir):
37
- makedirs(build_dir)
38
+ if not build_dir.is_dir():
39
+ build_dir.mkdir(parents=True)
38
40
 
39
41
  helpers.clean_build_dir = patched_clean_build_dir
40
42
  cli.clean_build_dir = patched_clean_build_dir
@@ -70,14 +72,19 @@ FILTER_PLATFORMIO_LINES = [
70
72
  r" - tool-esptool.* \(.*\)",
71
73
  r" - toolchain-.* \(.*\)",
72
74
  r"Creating BIN file .*",
75
+ r"Warning! Could not find file \".*.crt\"",
76
+ r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
77
+ r"Warning: DEPRECATED: 'esptool.py' is deprecated. Please use 'esptool' instead. The '.py' suffix will be removed in a future major release.",
78
+ r"Warning: esp-idf-size exited with code 2",
79
+ r"esp_idf_size: error: unrecognized arguments: --ng",
73
80
  ]
74
81
 
75
82
 
76
83
  def run_platformio_cli(*args, **kwargs) -> str | int:
77
84
  os.environ["PLATFORMIO_FORCE_COLOR"] = "true"
78
- os.environ["PLATFORMIO_BUILD_DIR"] = os.path.abspath(CORE.relative_pioenvs_path())
85
+ os.environ["PLATFORMIO_BUILD_DIR"] = str(CORE.relative_pioenvs_path().absolute())
79
86
  os.environ.setdefault(
80
- "PLATFORMIO_LIBDEPS_DIR", os.path.abspath(CORE.relative_piolibdeps_path())
87
+ "PLATFORMIO_LIBDEPS_DIR", str(CORE.relative_piolibdeps_path().absolute())
81
88
  )
82
89
  # Suppress Python syntax warnings from third-party scripts during compilation
83
90
  os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
@@ -96,7 +103,7 @@ def run_platformio_cli(*args, **kwargs) -> str | int:
96
103
 
97
104
 
98
105
  def run_platformio_cli_run(config, verbose, *args, **kwargs) -> str | int:
99
- command = ["run", "-d", CORE.build_path]
106
+ command = ["run", "-d", str(CORE.build_path)]
100
107
  if verbose:
101
108
  command += ["-v"]
102
109
  command += list(args)
@@ -128,8 +135,8 @@ def _run_idedata(config):
128
135
 
129
136
 
130
137
  def _load_idedata(config):
131
- platformio_ini = Path(CORE.relative_build_path("platformio.ini"))
132
- temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json"))
138
+ platformio_ini = CORE.relative_build_path("platformio.ini")
139
+ temp_idedata = CORE.relative_internal_path("idedata", f"{CORE.name}.json")
133
140
 
134
141
  changed = False
135
142
  if (
@@ -299,7 +306,7 @@ def process_stacktrace(config, line, backtrace_state):
299
306
 
300
307
  @dataclass
301
308
  class FlashImage:
302
- path: str
309
+ path: Path
303
310
  offset: str
304
311
 
305
312
 
@@ -308,17 +315,17 @@ class IDEData:
308
315
  self.raw = raw
309
316
 
310
317
  @property
311
- def firmware_elf_path(self):
312
- return self.raw["prog_path"]
318
+ def firmware_elf_path(self) -> Path:
319
+ return Path(self.raw["prog_path"])
313
320
 
314
321
  @property
315
- def firmware_bin_path(self) -> str:
316
- return str(Path(self.firmware_elf_path).with_suffix(".bin"))
322
+ def firmware_bin_path(self) -> Path:
323
+ return self.firmware_elf_path.with_suffix(".bin")
317
324
 
318
325
  @property
319
326
  def extra_flash_images(self) -> list[FlashImage]:
320
327
  return [
321
- FlashImage(path=entry["path"], offset=entry["offset"])
328
+ FlashImage(path=Path(entry["path"]), offset=entry["offset"])
322
329
  for entry in self.raw["extra"]["flash_images"]
323
330
  ]
324
331
 
esphome/storage_json.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import binascii
4
- import codecs
5
4
  from datetime import datetime
6
5
  import json
7
6
  import logging
8
7
  import os
8
+ from pathlib import Path
9
9
 
10
10
  from esphome import const
11
11
  from esphome.const import CONF_DISABLED, CONF_MDNS
@@ -16,30 +16,35 @@ from esphome.types import CoreType
16
16
  _LOGGER = logging.getLogger(__name__)
17
17
 
18
18
 
19
- def storage_path() -> str:
20
- return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json")
19
+ def storage_path() -> Path:
20
+ return CORE.data_dir / "storage" / f"{CORE.config_filename}.json"
21
21
 
22
22
 
23
- def ext_storage_path(config_filename: str) -> str:
24
- return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json")
23
+ def ext_storage_path(config_filename: str) -> Path:
24
+ return CORE.data_dir / "storage" / f"{config_filename}.json"
25
25
 
26
26
 
27
- def esphome_storage_path() -> str:
28
- return os.path.join(CORE.data_dir, "esphome.json")
27
+ def esphome_storage_path() -> Path:
28
+ return CORE.data_dir / "esphome.json"
29
29
 
30
30
 
31
- def ignored_devices_storage_path() -> str:
32
- return os.path.join(CORE.data_dir, "ignored-devices.json")
31
+ def ignored_devices_storage_path() -> Path:
32
+ return CORE.data_dir / "ignored-devices.json"
33
33
 
34
34
 
35
- def trash_storage_path() -> str:
35
+ def trash_storage_path() -> Path:
36
36
  return CORE.relative_config_path("trash")
37
37
 
38
38
 
39
- def archive_storage_path() -> str:
39
+ def archive_storage_path() -> Path:
40
40
  return CORE.relative_config_path("archive")
41
41
 
42
42
 
43
+ def _to_path_if_not_none(value: str | None) -> Path | None:
44
+ """Convert a string to Path if it's not None."""
45
+ return Path(value) if value is not None else None
46
+
47
+
43
48
  class StorageJSON:
44
49
  def __init__(
45
50
  self,
@@ -52,8 +57,8 @@ class StorageJSON:
52
57
  address: str,
53
58
  web_port: int | None,
54
59
  target_platform: str,
55
- build_path: str | None,
56
- firmware_bin_path: str | None,
60
+ build_path: Path | None,
61
+ firmware_bin_path: Path | None,
57
62
  loaded_integrations: set[str],
58
63
  loaded_platforms: set[str],
59
64
  no_mdns: bool,
@@ -107,8 +112,8 @@ class StorageJSON:
107
112
  "address": self.address,
108
113
  "web_port": self.web_port,
109
114
  "esp_platform": self.target_platform,
110
- "build_path": self.build_path,
111
- "firmware_bin_path": self.firmware_bin_path,
115
+ "build_path": str(self.build_path),
116
+ "firmware_bin_path": str(self.firmware_bin_path),
112
117
  "loaded_integrations": sorted(self.loaded_integrations),
113
118
  "loaded_platforms": sorted(self.loaded_platforms),
114
119
  "no_mdns": self.no_mdns,
@@ -176,8 +181,8 @@ class StorageJSON:
176
181
  )
177
182
 
178
183
  @staticmethod
179
- def _load_impl(path: str) -> StorageJSON | None:
180
- with codecs.open(path, "r", encoding="utf-8") as f_handle:
184
+ def _load_impl(path: Path) -> StorageJSON | None:
185
+ with path.open("r", encoding="utf-8") as f_handle:
181
186
  storage = json.load(f_handle)
182
187
  storage_version = storage["storage_version"]
183
188
  name = storage.get("name")
@@ -190,8 +195,8 @@ class StorageJSON:
190
195
  address = storage.get("address")
191
196
  web_port = storage.get("web_port")
192
197
  esp_platform = storage.get("esp_platform")
193
- build_path = storage.get("build_path")
194
- firmware_bin_path = storage.get("firmware_bin_path")
198
+ build_path = _to_path_if_not_none(storage.get("build_path"))
199
+ firmware_bin_path = _to_path_if_not_none(storage.get("firmware_bin_path"))
195
200
  loaded_integrations = set(storage.get("loaded_integrations", []))
196
201
  loaded_platforms = set(storage.get("loaded_platforms", []))
197
202
  no_mdns = storage.get("no_mdns", False)
@@ -217,7 +222,7 @@ class StorageJSON:
217
222
  )
218
223
 
219
224
  @staticmethod
220
- def load(path: str) -> StorageJSON | None:
225
+ def load(path: Path) -> StorageJSON | None:
221
226
  try:
222
227
  return StorageJSON._load_impl(path)
223
228
  except Exception: # pylint: disable=broad-except
@@ -268,7 +273,7 @@ class EsphomeStorageJSON:
268
273
 
269
274
  @staticmethod
270
275
  def _load_impl(path: str) -> EsphomeStorageJSON | None:
271
- with codecs.open(path, "r", encoding="utf-8") as f_handle:
276
+ with Path(path).open("r", encoding="utf-8") as f_handle:
272
277
  storage = json.load(f_handle)
273
278
  storage_version = storage["storage_version"]
274
279
  cookie_secret = storage.get("cookie_secret")
esphome/types.py CHANGED
@@ -1,8 +1,10 @@
1
1
  """This helper module tracks commonly used types in the esphome python codebase."""
2
2
 
3
- from typing import TypedDict
3
+ import abc
4
+ from collections.abc import Sequence
5
+ from typing import Any, TypedDict
4
6
 
5
- from esphome.core import ID, EsphomeCore, Lambda
7
+ from esphome.core import ID, EsphomeCore, Lambda, TimePeriod
6
8
 
7
9
  ConfigFragmentType = (
8
10
  str
@@ -20,6 +22,32 @@ CoreType = EsphomeCore
20
22
  ConfigPathType = str | int
21
23
 
22
24
 
25
+ class Expression(abc.ABC):
26
+ __slots__ = ()
27
+
28
+ @abc.abstractmethod
29
+ def __str__(self):
30
+ """
31
+ Convert expression into C++ code
32
+ """
33
+
34
+
35
+ SafeExpType = (
36
+ Expression
37
+ | bool
38
+ | str
39
+ | int
40
+ | float
41
+ | TimePeriod
42
+ | type[bool]
43
+ | type[int]
44
+ | type[float]
45
+ | Sequence[Any]
46
+ )
47
+
48
+ TemplateArgsType = list[tuple[SafeExpType, str]]
49
+
50
+
23
51
  class EntityMetadata(TypedDict):
24
52
  """Metadata stored for each entity to help with duplicate detection."""
25
53
 
esphome/util.py CHANGED
@@ -1,20 +1,30 @@
1
1
  import collections
2
+ from collections.abc import Callable
2
3
  import io
3
4
  import logging
4
- import os
5
5
  from pathlib import Path
6
6
  import re
7
7
  import subprocess
8
8
  import sys
9
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from esphome import const
12
12
 
13
13
  _LOGGER = logging.getLogger(__name__)
14
14
 
15
+ if TYPE_CHECKING:
16
+ from esphome.config_validation import Schema
17
+ from esphome.cpp_generator import MockObjClass
18
+
15
19
 
16
20
  class RegistryEntry:
17
- def __init__(self, name, fun, type_id, schema):
21
+ def __init__(
22
+ self,
23
+ name: str,
24
+ fun: Callable[..., Any],
25
+ type_id: "MockObjClass",
26
+ schema: "Schema",
27
+ ):
18
28
  self.name = name
19
29
  self.fun = fun
20
30
  self.type_id = type_id
@@ -39,8 +49,8 @@ class Registry(dict[str, RegistryEntry]):
39
49
  self.base_schema = base_schema or {}
40
50
  self.type_id_key = type_id_key
41
51
 
42
- def register(self, name, type_id, schema):
43
- def decorator(fun):
52
+ def register(self, name: str, type_id: "MockObjClass", schema: "Schema"):
53
+ def decorator(fun: Callable[..., Any]):
44
54
  self[name] = RegistryEntry(name, fun, type_id, schema)
45
55
  return fun
46
56
 
@@ -48,8 +58,8 @@ class Registry(dict[str, RegistryEntry]):
48
58
 
49
59
 
50
60
  class SimpleRegistry(dict):
51
- def register(self, name, data):
52
- def decorator(fun):
61
+ def register(self, name: str, data: Any):
62
+ def decorator(fun: Callable[..., Any]):
53
63
  self[name] = (fun, data)
54
64
  return fun
55
65
 
@@ -86,7 +96,10 @@ def safe_input(prompt=""):
86
96
  return input()
87
97
 
88
98
 
89
- def shlex_quote(s):
99
+ def shlex_quote(s: str | Path) -> str:
100
+ # Convert Path objects to strings
101
+ if isinstance(s, Path):
102
+ s = str(s)
90
103
  if not s:
91
104
  return "''"
92
105
  if re.search(r"[^\w@%+=:,./-]", s) is None:
@@ -272,25 +285,28 @@ class OrderedDict(collections.OrderedDict):
272
285
  return dict(self).__repr__()
273
286
 
274
287
 
275
- def list_yaml_files(configs: list[str]) -> list[str]:
276
- files: list[str] = []
288
+ def list_yaml_files(configs: list[str | Path]) -> list[Path]:
289
+ files: list[Path] = []
277
290
  for config in configs:
278
- if os.path.isfile(config):
291
+ config = Path(config)
292
+ if not config.exists():
293
+ raise FileNotFoundError(f"Config path '{config}' does not exist!")
294
+ if config.is_file():
279
295
  files.append(config)
280
296
  else:
281
- files.extend(os.path.join(config, p) for p in os.listdir(config))
297
+ files.extend(config.glob("*"))
282
298
  files = filter_yaml_files(files)
283
299
  return sorted(files)
284
300
 
285
301
 
286
- def filter_yaml_files(files: list[str]) -> list[str]:
302
+ def filter_yaml_files(files: list[Path]) -> list[Path]:
287
303
  return [
288
304
  f
289
305
  for f in files
290
306
  if (
291
- os.path.splitext(f)[1] in (".yaml", ".yml")
292
- and os.path.basename(f) not in ("secrets.yaml", "secrets.yml")
293
- and not os.path.basename(f).startswith(".")
307
+ f.suffix in (".yaml", ".yml")
308
+ and f.name not in ("secrets.yaml", "secrets.yml")
309
+ and not f.name.startswith(".")
294
310
  )
295
311
  ]
296
312
 
esphome/vscode.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from io import StringIO
4
4
  import json
5
- import os
5
+ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  from esphome.config import Config, _format_vol_invalid, validate_config
@@ -67,24 +67,24 @@ def _read_file_content_from_json_on_stdin() -> str:
67
67
  return data["content"]
68
68
 
69
69
 
70
- def _print_file_read_event(path: str) -> None:
70
+ def _print_file_read_event(path: Path) -> None:
71
71
  """Print a file read event."""
72
72
  print(
73
73
  json.dumps(
74
74
  {
75
75
  "type": "read_file",
76
- "path": path,
76
+ "path": str(path),
77
77
  }
78
78
  )
79
79
  )
80
80
 
81
81
 
82
- def _request_and_get_stream_on_stdin(fname: str) -> StringIO:
82
+ def _request_and_get_stream_on_stdin(fname: Path) -> StringIO:
83
83
  _print_file_read_event(fname)
84
84
  return StringIO(_read_file_content_from_json_on_stdin())
85
85
 
86
86
 
87
- def _vscode_loader(fname: str) -> dict[str, Any]:
87
+ def _vscode_loader(fname: Path) -> dict[str, Any]:
88
88
  raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
89
89
  # it is required to set the name on StringIO so document on start_mark
90
90
  # is set properly. Otherwise it is initialized with "<file>"
@@ -92,7 +92,7 @@ def _vscode_loader(fname: str) -> dict[str, Any]:
92
92
  return parse_yaml(fname, raw_yaml_stream, _vscode_loader)
93
93
 
94
94
 
95
- def _ace_loader(fname: str) -> dict[str, Any]:
95
+ def _ace_loader(fname: Path) -> dict[str, Any]:
96
96
  raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
97
97
  return parse_yaml(fname, raw_yaml_stream)
98
98
 
@@ -120,10 +120,10 @@ def read_config(args):
120
120
  return
121
121
  CORE.vscode = True
122
122
  if args.ace: # Running from ESPHome Compiler dashboard, not vscode
123
- CORE.config_path = os.path.join(args.configuration, data["file"])
123
+ CORE.config_path = Path(args.configuration) / data["file"]
124
124
  loader = _ace_loader
125
125
  else:
126
- CORE.config_path = data["file"]
126
+ CORE.config_path = Path(data["file"])
127
127
  loader = _vscode_loader
128
128
 
129
129
  file_name = CORE.config_path
esphome/wizard.py CHANGED
@@ -1,4 +1,4 @@
1
- import os
1
+ from pathlib import Path
2
2
  import random
3
3
  import string
4
4
  from typing import Literal, NotRequired, TypedDict, Unpack
@@ -213,7 +213,7 @@ class WizardWriteKwargs(TypedDict):
213
213
  file_text: NotRequired[str]
214
214
 
215
215
 
216
- def wizard_write(path: str, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
216
+ def wizard_write(path: Path, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
217
217
  from esphome.components.bk72xx import boards as bk72xx_boards
218
218
  from esphome.components.esp32 import boards as esp32_boards
219
219
  from esphome.components.esp8266 import boards as esp8266_boards
@@ -256,13 +256,13 @@ def wizard_write(path: str, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
256
256
  file_text = wizard_file(**kwargs)
257
257
 
258
258
  # Check if file already exists to prevent overwriting
259
- if os.path.exists(path) and os.path.isfile(path):
259
+ if path.exists() and path.is_file():
260
260
  safe_print(color(AnsiFore.RED, f'The file "{path}" already exists.'))
261
261
  return False
262
262
 
263
263
  write_file(path, file_text)
264
264
  storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
265
- storage_path = ext_storage_path(os.path.basename(path))
265
+ storage_path = ext_storage_path(path.name)
266
266
  storage.save(storage_path)
267
267
 
268
268
  return True
@@ -301,7 +301,7 @@ def strip_accents(value: str) -> str:
301
301
  )
302
302
 
303
303
 
304
- def wizard(path: str) -> int:
304
+ def wizard(path: Path) -> int:
305
305
  from esphome.components.bk72xx import boards as bk72xx_boards
306
306
  from esphome.components.esp32 import boards as esp32_boards
307
307
  from esphome.components.esp8266 import boards as esp8266_boards
@@ -309,14 +309,14 @@ def wizard(path: str) -> int:
309
309
  from esphome.components.rp2040 import boards as rp2040_boards
310
310
  from esphome.components.rtl87xx import boards as rtl87xx_boards
311
311
 
312
- if not path.endswith(".yaml") and not path.endswith(".yml"):
312
+ if path.suffix not in (".yaml", ".yml"):
313
313
  safe_print(
314
- f"Please make your configuration file {color(AnsiFore.CYAN, path)} have the extension .yaml or .yml"
314
+ f"Please make your configuration file {color(AnsiFore.CYAN, str(path))} have the extension .yaml or .yml"
315
315
  )
316
316
  return 1
317
- if os.path.exists(path):
317
+ if path.exists():
318
318
  safe_print(
319
- f"Uh oh, it seems like {color(AnsiFore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
319
+ f"Uh oh, it seems like {color(AnsiFore.CYAN, str(path))} already exists, please delete that file first or chose another configuration file."
320
320
  )
321
321
  return 2
322
322
 
@@ -549,7 +549,7 @@ def wizard(path: str) -> int:
549
549
  safe_print()
550
550
  safe_print(
551
551
  color(AnsiFore.CYAN, "DONE! I've now written a new configuration file to ")
552
- + color(AnsiFore.BOLD_CYAN, path)
552
+ + color(AnsiFore.BOLD_CYAN, str(path))
553
553
  )
554
554
  safe_print()
555
555
  safe_print("Next steps:")
esphome/writer.py CHANGED
@@ -266,7 +266,7 @@ def generate_version_h():
266
266
 
267
267
  def write_cpp(code_s):
268
268
  path = CORE.relative_src_path("main.cpp")
269
- if os.path.isfile(path):
269
+ if path.is_file():
270
270
  text = read_file(path)
271
271
  code_format = find_begin_end(
272
272
  text, CPP_AUTO_GENERATE_BEGIN, CPP_AUTO_GENERATE_END
@@ -292,43 +292,79 @@ def write_cpp(code_s):
292
292
 
293
293
  def clean_cmake_cache():
294
294
  pioenvs = CORE.relative_pioenvs_path()
295
- if os.path.isdir(pioenvs):
296
- pioenvs_cmake_path = CORE.relative_pioenvs_path(CORE.name, "CMakeCache.txt")
297
- if os.path.isfile(pioenvs_cmake_path):
295
+ if pioenvs.is_dir():
296
+ pioenvs_cmake_path = pioenvs / CORE.name / "CMakeCache.txt"
297
+ if pioenvs_cmake_path.is_file():
298
298
  _LOGGER.info("Deleting %s", pioenvs_cmake_path)
299
- os.remove(pioenvs_cmake_path)
299
+ pioenvs_cmake_path.unlink()
300
300
 
301
301
 
302
302
  def clean_build():
303
303
  import shutil
304
304
 
305
+ # Allow skipping cache cleaning for integration tests
306
+ if os.environ.get("ESPHOME_SKIP_CLEAN_BUILD"):
307
+ _LOGGER.warning("Skipping build cleaning (ESPHOME_SKIP_CLEAN_BUILD set)")
308
+ return
309
+
305
310
  pioenvs = CORE.relative_pioenvs_path()
306
- if os.path.isdir(pioenvs):
311
+ if pioenvs.is_dir():
307
312
  _LOGGER.info("Deleting %s", pioenvs)
308
313
  shutil.rmtree(pioenvs)
309
314
  piolibdeps = CORE.relative_piolibdeps_path()
310
- if os.path.isdir(piolibdeps):
315
+ if piolibdeps.is_dir():
311
316
  _LOGGER.info("Deleting %s", piolibdeps)
312
317
  shutil.rmtree(piolibdeps)
313
318
  dependencies_lock = CORE.relative_build_path("dependencies.lock")
314
- if os.path.isfile(dependencies_lock):
319
+ if dependencies_lock.is_file():
315
320
  _LOGGER.info("Deleting %s", dependencies_lock)
316
- os.remove(dependencies_lock)
321
+ dependencies_lock.unlink()
317
322
 
318
323
  # Clean PlatformIO cache to resolve CMake compiler detection issues
319
324
  # This helps when toolchain paths change or get corrupted
320
325
  try:
321
- from platformio.project.helpers import get_project_cache_dir
326
+ from platformio.project.config import ProjectConfig
322
327
  except ImportError:
323
328
  # PlatformIO is not available, skip cache cleaning
324
329
  pass
325
330
  else:
326
- cache_dir = get_project_cache_dir()
327
- if cache_dir and cache_dir.strip() and os.path.isdir(cache_dir):
331
+ config = ProjectConfig.get_instance()
332
+ cache_dir = Path(config.get("platformio", "cache_dir"))
333
+ if cache_dir.is_dir():
328
334
  _LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
329
335
  shutil.rmtree(cache_dir)
330
336
 
331
337
 
338
+ def clean_all(configuration: list[str]):
339
+ import shutil
340
+
341
+ # Clean entire build dir
342
+ for dir in configuration:
343
+ build_dir = Path(dir) / ".esphome"
344
+ if build_dir.is_dir():
345
+ _LOGGER.info("Cleaning %s", build_dir)
346
+ # Don't remove storage as it will cause the dashboard to regenerate all configs
347
+ for item in build_dir.iterdir():
348
+ if item.is_file():
349
+ item.unlink()
350
+ elif item.name != "storage" and item.is_dir():
351
+ shutil.rmtree(item)
352
+
353
+ # Clean PlatformIO project files
354
+ try:
355
+ from platformio.project.config import ProjectConfig
356
+ except ImportError:
357
+ # PlatformIO is not available, skip cleaning
358
+ pass
359
+ else:
360
+ config = ProjectConfig.get_instance()
361
+ for pio_dir in ["cache_dir", "packages_dir", "platforms_dir", "core_dir"]:
362
+ path = Path(config.get("platformio", pio_dir))
363
+ if path.is_dir():
364
+ _LOGGER.info("Deleting PlatformIO %s %s", pio_dir, path)
365
+ shutil.rmtree(path)
366
+
367
+
332
368
  GITIGNORE_CONTENT = """# Gitignore settings for ESPHome
333
369
  # This is an example and may include too much for your use-case.
334
370
  # You can modify this file to suit your needs.
@@ -339,6 +375,5 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome
339
375
 
340
376
  def write_gitignore():
341
377
  path = CORE.relative_config_path(".gitignore")
342
- if not os.path.isfile(path):
343
- with open(file=path, mode="w", encoding="utf-8") as f:
344
- f.write(GITIGNORE_CONTENT)
378
+ if not path.is_file():
379
+ path.write_text(GITIGNORE_CONTENT, encoding="utf-8")