esphome 2025.9.3__py3-none-any.whl → 2025.10.0b2__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 (351) hide show
  1. esphome/__main__.py +94 -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 +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_spi/display.py +24 -8
  167. esphome/components/mipi_spi/mipi_spi.h +3 -3
  168. esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
  169. esphome/components/mmc5603/mmc5603.cpp +3 -3
  170. esphome/components/modbus/modbus.cpp +27 -13
  171. esphome/components/modbus/modbus.h +5 -3
  172. esphome/components/modbus/modbus_definitions.h +86 -0
  173. esphome/components/modbus_controller/__init__.py +29 -1
  174. esphome/components/modbus_controller/const.py +4 -0
  175. esphome/components/modbus_controller/modbus_controller.cpp +38 -13
  176. esphome/components/modbus_controller/modbus_controller.h +18 -29
  177. esphome/components/mpr121/mpr121.cpp +41 -42
  178. esphome/components/mpr121/mpr121.h +0 -1
  179. esphome/components/nau7802/nau7802.cpp +2 -2
  180. esphome/components/network/__init__.py +7 -3
  181. esphome/components/nextion/display.py +4 -4
  182. esphome/components/nextion/nextion.cpp +8 -8
  183. esphome/components/number/__init__.py +2 -0
  184. esphome/components/number/number_call.cpp +23 -12
  185. esphome/components/number/number_call.h +5 -0
  186. esphome/components/online_image/bmp_image.cpp +2 -1
  187. esphome/components/online_image/jpeg_image.cpp +4 -2
  188. esphome/components/opentherm/opentherm.cpp +5 -5
  189. esphome/components/opentherm/opentherm.h +3 -3
  190. esphome/components/openthread/openthread.cpp +11 -10
  191. esphome/components/openthread/openthread.h +0 -1
  192. esphome/components/ota/ota_backend.h +1 -0
  193. esphome/components/packages/__init__.py +10 -8
  194. esphome/components/packet_transport/packet_transport.cpp +2 -0
  195. esphome/components/pid/pid_controller.cpp +1 -1
  196. esphome/components/prometheus/prometheus_handler.cpp +239 -239
  197. esphome/components/psram/__init__.py +30 -28
  198. esphome/components/qmc5883l/qmc5883l.cpp +15 -0
  199. esphome/components/qmc5883l/qmc5883l.h +3 -0
  200. esphome/components/qmc5883l/sensor.py +31 -12
  201. esphome/components/remote_base/gobox_protocol.cpp +3 -3
  202. esphome/components/remote_receiver/__init__.py +14 -2
  203. esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
  204. esphome/components/remote_receiver/remote_receiver.h +4 -0
  205. esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
  206. esphome/components/remote_transmitter/__init__.py +2 -2
  207. esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
  208. esphome/components/rp2040/__init__.py +11 -11
  209. esphome/components/rtttl/rtttl.cpp +2 -2
  210. esphome/components/scd30/sensor.py +1 -1
  211. esphome/components/script/__init__.py +1 -1
  212. esphome/components/script/script.h +7 -7
  213. esphome/components/select/select.cpp +5 -4
  214. esphome/components/select/select_call.cpp +1 -1
  215. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  216. esphome/components/sensor/__init__.py +2 -0
  217. esphome/components/sha256/__init__.py +22 -0
  218. esphome/components/sha256/sha256.cpp +116 -0
  219. esphome/components/sha256/sha256.h +60 -0
  220. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  221. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  222. esphome/components/spi/__init__.py +0 -3
  223. esphome/components/split_buffer/__init__.py +5 -0
  224. esphome/components/split_buffer/split_buffer.cpp +133 -0
  225. esphome/components/split_buffer/split_buffer.h +40 -0
  226. esphome/components/sps30/sps30.cpp +14 -10
  227. esphome/components/sps30/sps30.h +2 -0
  228. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  229. esphome/components/st7789v/st7789v.cpp +3 -2
  230. esphome/components/statsd/statsd.cpp +1 -1
  231. esphome/components/substitutions/__init__.py +3 -1
  232. esphome/components/substitutions/jinja.py +13 -3
  233. esphome/components/sx126x/__init__.py +16 -0
  234. esphome/components/sx126x/sx126x.cpp +15 -1
  235. esphome/components/sx126x/sx126x.h +9 -1
  236. esphome/components/sx126x/sx126x_reg.h +2 -0
  237. esphome/components/text_sensor/text_sensor.cpp +16 -0
  238. esphome/components/text_sensor/text_sensor.h +3 -10
  239. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  240. esphome/components/tuya/select/tuya_select.cpp +1 -1
  241. esphome/components/tuya/tuya.cpp +29 -4
  242. esphome/components/uart/__init__.py +37 -27
  243. esphome/components/uart/uart.h +6 -0
  244. esphome/components/uart/uart_component.cpp +8 -0
  245. esphome/components/uart/uart_component.h +28 -0
  246. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  247. esphome/components/uart/uart_component_esp_idf.h +5 -2
  248. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  249. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  250. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  251. esphome/components/usb_host/__init__.py +12 -2
  252. esphome/components/usb_host/usb_host.h +89 -14
  253. esphome/components/usb_host/usb_host_client.cpp +157 -22
  254. esphome/components/usb_host/usb_host_component.cpp +1 -1
  255. esphome/components/usb_uart/__init__.py +0 -1
  256. esphome/components/usb_uart/ch34x.cpp +4 -4
  257. esphome/components/usb_uart/cp210x.cpp +3 -3
  258. esphome/components/usb_uart/usb_uart.cpp +88 -32
  259. esphome/components/usb_uart/usb_uart.h +30 -6
  260. esphome/components/valve/valve.cpp +1 -0
  261. esphome/components/veml7700/veml7700.cpp +7 -6
  262. esphome/components/version/version_text_sensor.cpp +2 -1
  263. esphome/components/voice_assistant/voice_assistant.cpp +3 -2
  264. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  265. esphome/components/web_server/list_entities.cpp +3 -4
  266. esphome/components/web_server/list_entities.h +8 -10
  267. esphome/components/web_server/ota/__init__.py +1 -1
  268. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  269. esphome/components/web_server/web_server.cpp +509 -404
  270. esphome/components/web_server/web_server.h +5 -6
  271. esphome/components/web_server/web_server_v1.cpp +21 -19
  272. esphome/components/web_server_base/__init__.py +5 -2
  273. esphome/components/web_server_base/web_server_base.h +27 -7
  274. esphome/components/web_server_idf/__init__.py +1 -1
  275. esphome/components/web_server_idf/multipart.cpp +2 -2
  276. esphome/components/web_server_idf/multipart.h +2 -2
  277. esphome/components/web_server_idf/utils.cpp +2 -2
  278. esphome/components/web_server_idf/utils.h +2 -2
  279. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  280. esphome/components/web_server_idf/web_server_idf.h +12 -10
  281. esphome/components/wifi/__init__.py +13 -11
  282. esphome/components/wifi/wifi_component.cpp +74 -56
  283. esphome/components/wifi/wifi_component.h +4 -4
  284. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  285. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  286. esphome/components/wireguard/__init__.py +1 -1
  287. esphome/components/wts01/__init__.py +0 -0
  288. esphome/components/wts01/sensor.py +41 -0
  289. esphome/components/wts01/wts01.cpp +91 -0
  290. esphome/components/wts01/wts01.h +27 -0
  291. esphome/components/zephyr/__init__.py +5 -5
  292. esphome/components/zwave_proxy/__init__.py +43 -0
  293. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  294. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  295. esphome/config.py +79 -24
  296. esphome/config_validation.py +13 -15
  297. esphome/const.py +9 -2
  298. esphome/core/__init__.py +33 -22
  299. esphome/core/component.cpp +28 -18
  300. esphome/core/component_iterator.h +2 -1
  301. esphome/core/config.py +15 -15
  302. esphome/core/defines.h +21 -0
  303. esphome/core/entity_helpers.py +9 -6
  304. esphome/core/hash_base.h +56 -0
  305. esphome/core/helpers.cpp +19 -3
  306. esphome/core/helpers.h +26 -0
  307. esphome/core/scheduler.cpp +5 -21
  308. esphome/core/scheduler.h +19 -8
  309. esphome/core/string_ref.h +1 -1
  310. esphome/core/time.cpp +5 -5
  311. esphome/cpp_generator.py +4 -29
  312. esphome/dashboard/const.py +21 -4
  313. esphome/dashboard/core.py +10 -8
  314. esphome/dashboard/dns.py +15 -0
  315. esphome/dashboard/entries.py +15 -21
  316. esphome/dashboard/models.py +76 -0
  317. esphome/dashboard/settings.py +7 -7
  318. esphome/dashboard/status/mdns.py +46 -2
  319. esphome/dashboard/web_server.py +367 -93
  320. esphome/espota2.py +112 -32
  321. esphome/external_files.py +6 -7
  322. esphome/git.py +8 -0
  323. esphome/helpers.py +124 -77
  324. esphome/loader.py +8 -9
  325. esphome/pins.py +2 -2
  326. esphome/platformio_api.py +56 -18
  327. esphome/storage_json.py +26 -21
  328. esphome/types.py +30 -2
  329. esphome/util.py +32 -16
  330. esphome/vscode.py +8 -8
  331. esphome/wizard.py +10 -10
  332. esphome/writer.py +50 -15
  333. esphome/yaml_util.py +37 -31
  334. esphome/zeroconf.py +12 -3
  335. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +12 -12
  336. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +340 -320
  337. esphome/components/event_emitter/__init__.py +0 -5
  338. esphome/components/event_emitter/event_emitter.cpp +0 -14
  339. esphome/components/event_emitter/event_emitter.h +0 -63
  340. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  341. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  342. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  343. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  344. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  345. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  346. esphome/core/string_ref.cpp +0 -12
  347. esphome/dashboard/util/file.py +0 -63
  348. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
  349. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
  350. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
  351. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@ from ipaddress import (
15
15
  ip_network,
16
16
  )
17
17
  import logging
18
- import os
18
+ from pathlib import Path
19
19
  import re
20
20
  from string import ascii_letters, digits
21
21
  import uuid as uuid_
@@ -1609,34 +1609,32 @@ def dimensions(value):
1609
1609
  return dimensions([match.group(1), match.group(2)])
1610
1610
 
1611
1611
 
1612
- def directory(value):
1612
+ def directory(value: object) -> Path:
1613
1613
  value = string(value)
1614
1614
  path = CORE.relative_config_path(value)
1615
1615
 
1616
- if not os.path.exists(path):
1616
+ if not path.exists():
1617
1617
  raise Invalid(
1618
- f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
1618
+ f"Could not find directory '{path}'. Please make sure it exists (full path: {path.resolve()})."
1619
1619
  )
1620
- if not os.path.isdir(path):
1620
+ if not path.is_dir():
1621
1621
  raise Invalid(
1622
- f"Path '{path}' is not a directory (full path: {os.path.abspath(path)})."
1622
+ f"Path '{path}' is not a directory (full path: {path.resolve()})."
1623
1623
  )
1624
- return value
1624
+ return path
1625
1625
 
1626
1626
 
1627
- def file_(value):
1627
+ def file_(value: object) -> Path:
1628
1628
  value = string(value)
1629
1629
  path = CORE.relative_config_path(value)
1630
1630
 
1631
- if not os.path.exists(path):
1632
- raise Invalid(
1633
- f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
1634
- )
1635
- if not os.path.isfile(path):
1631
+ if not path.exists():
1636
1632
  raise Invalid(
1637
- f"Path '{path}' is not a file (full path: {os.path.abspath(path)})."
1633
+ f"Could not find file '{path}'. Please make sure it exists (full path: {path.resolve()})."
1638
1634
  )
1639
- return value
1635
+ if not path.is_file():
1636
+ raise Invalid(f"Path '{path}' is not a file (full path: {path.resolve()}).")
1637
+ return path
1640
1638
 
1641
1639
 
1642
1640
  ENTITY_ID_CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789_"
esphome/const.py CHANGED
@@ -4,7 +4,7 @@ from enum import Enum
4
4
 
5
5
  from esphome.enum import StrEnum
6
6
 
7
- __version__ = "2025.9.3"
7
+ __version__ = "2025.10.0b2"
8
8
 
9
9
  ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
10
10
  VALID_SUBSTITUTIONS_CHARACTERS = (
@@ -174,6 +174,7 @@ CONF_CALIBRATE_LINEAR = "calibrate_linear"
174
174
  CONF_CALIBRATION = "calibration"
175
175
  CONF_CAPACITANCE = "capacitance"
176
176
  CONF_CAPACITY = "capacity"
177
+ CONF_CAPTURE_RESPONSE = "capture_response"
177
178
  CONF_CARBON_MONOXIDE = "carbon_monoxide"
178
179
  CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent"
179
180
  CONF_CARRIER_FREQUENCY = "carrier_frequency"
@@ -186,6 +187,7 @@ CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
186
187
  CONF_CHECK = "check"
187
188
  CONF_CHIPSET = "chipset"
188
189
  CONF_CLEAN_SESSION = "clean_session"
190
+ CONF_CLEAR = "clear"
189
191
  CONF_CLEAR_IMPEDANCE = "clear_impedance"
190
192
  CONF_CLIENT_CERTIFICATE = "client_certificate"
191
193
  CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
@@ -541,6 +543,7 @@ CONF_MANUAL_IP = "manual_ip"
541
543
  CONF_MANUFACTURER_ID = "manufacturer_id"
542
544
  CONF_MASK_DISTURBER = "mask_disturber"
543
545
  CONF_MAX_BRIGHTNESS = "max_brightness"
546
+ CONF_MAX_CONNECTIONS = "max_connections"
544
547
  CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time"
545
548
  CONF_MAX_CURRENT = "max_current"
546
549
  CONF_MAX_DURATION = "max_duration"
@@ -670,9 +673,11 @@ CONF_ON_PRESET_SET = "on_preset_set"
670
673
  CONF_ON_PRESS = "on_press"
671
674
  CONF_ON_RAW_VALUE = "on_raw_value"
672
675
  CONF_ON_RELEASE = "on_release"
676
+ CONF_ON_RESPONSE = "on_response"
673
677
  CONF_ON_SHUTDOWN = "on_shutdown"
674
678
  CONF_ON_SPEED_SET = "on_speed_set"
675
679
  CONF_ON_STATE = "on_state"
680
+ CONF_ON_SUCCESS = "on_success"
676
681
  CONF_ON_TAG = "on_tag"
677
682
  CONF_ON_TAG_REMOVED = "on_tag_removed"
678
683
  CONF_ON_TIME = "on_time"
@@ -815,6 +820,7 @@ CONF_RESET_DURATION = "reset_duration"
815
820
  CONF_RESET_PIN = "reset_pin"
816
821
  CONF_RESIZE = "resize"
817
822
  CONF_RESOLUTION = "resolution"
823
+ CONF_RESPONSE_TEMPLATE = "response_template"
818
824
  CONF_RESTART = "restart"
819
825
  CONF_RESTORE = "restore"
820
826
  CONF_RESTORE_MODE = "restore_mode"
@@ -1167,7 +1173,7 @@ UNIT_KILOMETER = "km"
1167
1173
  UNIT_KILOMETER_PER_HOUR = "km/h"
1168
1174
  UNIT_KILOVOLT_AMPS = "kVA"
1169
1175
  UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
1170
- UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR"
1176
+ UNIT_KILOVOLT_AMPS_REACTIVE = "kvar"
1171
1177
  UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh"
1172
1178
  UNIT_KILOWATT = "kW"
1173
1179
  UNIT_KILOWATT_HOURS = "kWh"
@@ -1268,6 +1274,7 @@ DEVICE_CLASS_PLUG = "plug"
1268
1274
  DEVICE_CLASS_PM1 = "pm1"
1269
1275
  DEVICE_CLASS_PM10 = "pm10"
1270
1276
  DEVICE_CLASS_PM25 = "pm25"
1277
+ DEVICE_CLASS_PM4 = "pm4"
1271
1278
  DEVICE_CLASS_POWER = "power"
1272
1279
  DEVICE_CLASS_POWER_FACTOR = "power_factor"
1273
1280
  DEVICE_CLASS_PRECIPITATION = "precipitation"
esphome/core/__init__.py CHANGED
@@ -3,6 +3,7 @@ from contextlib import contextmanager
3
3
  import logging
4
4
  import math
5
5
  import os
6
+ from pathlib import Path
6
7
  import re
7
8
  from typing import TYPE_CHECKING
8
9
 
@@ -39,6 +40,8 @@ from esphome.helpers import ensure_unique_string, get_str_env, is_ha_addon
39
40
  from esphome.util import OrderedDict
40
41
 
41
42
  if TYPE_CHECKING:
43
+ from esphome.address_cache import AddressCache
44
+
42
45
  from ..cpp_generator import MockObj, MockObjClass, Statement
43
46
  from ..types import ConfigType, EntityMetadata
44
47
 
@@ -381,7 +384,7 @@ class DocumentLocation:
381
384
 
382
385
  @classmethod
383
386
  def from_mark(cls, mark):
384
- return cls(mark.name, mark.line, mark.column)
387
+ return cls(str(mark.name), mark.line, mark.column)
385
388
 
386
389
  def __str__(self):
387
390
  return f"{self.document} {self.line}:{self.column}"
@@ -526,6 +529,8 @@ class EsphomeCore:
526
529
  self.dashboard = False
527
530
  # True if command is run from vscode api
528
531
  self.vscode = False
532
+ # True if running in testing mode (disables validation checks for grouped testing)
533
+ self.testing_mode = False
529
534
  # The name of the node
530
535
  self.name: str | None = None
531
536
  # The friendly name of the node
@@ -536,9 +541,9 @@ class EsphomeCore:
536
541
  # The first key to this dict should always be the integration name
537
542
  self.data = {}
538
543
  # The relative path to the configuration YAML
539
- self.config_path: str | None = None
544
+ self.config_path: Path | None = None
540
545
  # The relative path to where all build files are stored
541
- self.build_path: str | None = None
546
+ self.build_path: Path | None = None
542
547
  # The validated configuration, this is None until the config has been validated
543
548
  self.config: ConfigType | None = None
544
549
  # The pending tasks in the task queue (mostly for C++ generation)
@@ -583,6 +588,8 @@ class EsphomeCore:
583
588
  self.id_classes = {}
584
589
  # The current component being processed during validation
585
590
  self.current_component: str | None = None
591
+ # Address cache for DNS and mDNS lookups from command line arguments
592
+ self.address_cache: AddressCache | None = None
586
593
 
587
594
  def reset(self):
588
595
  from esphome.pins import PIN_SCHEMA_REGISTRY
@@ -610,6 +617,7 @@ class EsphomeCore:
610
617
  self.platform_counts = defaultdict(int)
611
618
  self.unique_ids = {}
612
619
  self.current_component = None
620
+ self.address_cache = None
613
621
  PIN_SCHEMA_REGISTRY.reset()
614
622
 
615
623
  @contextmanager
@@ -659,43 +667,46 @@ class EsphomeCore:
659
667
  return None
660
668
 
661
669
  @property
662
- def config_dir(self):
663
- return os.path.abspath(os.path.dirname(self.config_path))
670
+ def config_dir(self) -> Path:
671
+ if self.config_path.is_dir():
672
+ return self.config_path.absolute()
673
+ return self.config_path.absolute().parent
664
674
 
665
675
  @property
666
- def data_dir(self):
676
+ def data_dir(self) -> Path:
667
677
  if is_ha_addon():
668
- return os.path.join("/data")
678
+ return Path("/data")
669
679
  if "ESPHOME_DATA_DIR" in os.environ:
670
- return get_str_env("ESPHOME_DATA_DIR", None)
680
+ return Path(get_str_env("ESPHOME_DATA_DIR", None))
671
681
  return self.relative_config_path(".esphome")
672
682
 
673
683
  @property
674
- def config_filename(self):
675
- return os.path.basename(self.config_path)
684
+ def config_filename(self) -> str:
685
+ return self.config_path.name
676
686
 
677
- def relative_config_path(self, *path):
678
- path_ = os.path.expanduser(os.path.join(*path))
679
- return os.path.join(self.config_dir, path_)
687
+ def relative_config_path(self, *path: str | Path) -> Path:
688
+ path_ = Path(*path).expanduser()
689
+ return self.config_dir / path_
680
690
 
681
- def relative_internal_path(self, *path: str) -> str:
682
- return os.path.join(self.data_dir, *path)
691
+ def relative_internal_path(self, *path: str | Path) -> Path:
692
+ path_ = Path(*path).expanduser()
693
+ return self.data_dir / path_
683
694
 
684
- def relative_build_path(self, *path):
685
- path_ = os.path.expanduser(os.path.join(*path))
686
- return os.path.join(self.build_path, path_)
695
+ def relative_build_path(self, *path: str | Path) -> Path:
696
+ path_ = Path(*path).expanduser()
697
+ return self.build_path / path_
687
698
 
688
- def relative_src_path(self, *path):
699
+ def relative_src_path(self, *path: str | Path) -> Path:
689
700
  return self.relative_build_path("src", *path)
690
701
 
691
- def relative_pioenvs_path(self, *path):
702
+ def relative_pioenvs_path(self, *path: str | Path) -> Path:
692
703
  return self.relative_build_path(".pioenvs", *path)
693
704
 
694
- def relative_piolibdeps_path(self, *path):
705
+ def relative_piolibdeps_path(self, *path: str | Path) -> Path:
695
706
  return self.relative_build_path(".piolibdeps", *path)
696
707
 
697
708
  @property
698
- def firmware_bin(self):
709
+ def firmware_bin(self) -> Path:
699
710
  if self.is_libretiny:
700
711
  return self.relative_pioenvs_path(self.name, "firmware.uf2")
701
712
  return self.relative_pioenvs_path(self.name, "firmware.bin")
@@ -33,12 +33,22 @@ static const char *const TAG = "component";
33
33
  // Using namespace-scope static to avoid guard variables (saves 16 bytes total)
34
34
  // This is safe because ESPHome is single-threaded during initialization
35
35
  namespace {
36
+ struct ComponentErrorMessage {
37
+ const Component *component;
38
+ const char *message;
39
+ };
40
+
41
+ struct ComponentPriorityOverride {
42
+ const Component *component;
43
+ float priority;
44
+ };
45
+
36
46
  // Error messages for failed components
37
47
  // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
38
- std::unique_ptr<std::vector<std::pair<const Component *, const char *>>> component_error_messages;
48
+ std::unique_ptr<std::vector<ComponentErrorMessage>> component_error_messages;
39
49
  // Setup priority overrides - freed after setup completes
40
50
  // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
41
- std::unique_ptr<std::vector<std::pair<const Component *, float>>> setup_priority_overrides;
51
+ std::unique_ptr<std::vector<ComponentPriorityOverride>> setup_priority_overrides;
42
52
  } // namespace
43
53
 
44
54
  namespace setup_priority {
@@ -134,9 +144,9 @@ void Component::call_dump_config() {
134
144
  // Look up error message from global vector
135
145
  const char *error_msg = nullptr;
136
146
  if (component_error_messages) {
137
- for (const auto &pair : *component_error_messages) {
138
- if (pair.first == this) {
139
- error_msg = pair.second;
147
+ for (const auto &entry : *component_error_messages) {
148
+ if (entry.component == this) {
149
+ error_msg = entry.message;
140
150
  break;
141
151
  }
142
152
  }
@@ -306,17 +316,17 @@ void Component::status_set_error(const char *message) {
306
316
  if (message != nullptr) {
307
317
  // Lazy allocate the error messages vector if needed
308
318
  if (!component_error_messages) {
309
- component_error_messages = std::make_unique<std::vector<std::pair<const Component *, const char *>>>();
319
+ component_error_messages = std::make_unique<std::vector<ComponentErrorMessage>>();
310
320
  }
311
321
  // Check if this component already has an error message
312
- for (auto &pair : *component_error_messages) {
313
- if (pair.first == this) {
314
- pair.second = message;
322
+ for (auto &entry : *component_error_messages) {
323
+ if (entry.component == this) {
324
+ entry.message = message;
315
325
  return;
316
326
  }
317
327
  }
318
328
  // Add new error message
319
- component_error_messages->emplace_back(this, message);
329
+ component_error_messages->emplace_back(ComponentErrorMessage{this, message});
320
330
  }
321
331
  }
322
332
  void Component::status_clear_warning() {
@@ -356,9 +366,9 @@ float Component::get_actual_setup_priority() const {
356
366
  // Check if there's an override in the global vector
357
367
  if (setup_priority_overrides) {
358
368
  // Linear search is fine for small n (typically < 5 overrides)
359
- for (const auto &pair : *setup_priority_overrides) {
360
- if (pair.first == this) {
361
- return pair.second;
369
+ for (const auto &entry : *setup_priority_overrides) {
370
+ if (entry.component == this) {
371
+ return entry.priority;
362
372
  }
363
373
  }
364
374
  }
@@ -367,21 +377,21 @@ float Component::get_actual_setup_priority() const {
367
377
  void Component::set_setup_priority(float priority) {
368
378
  // Lazy allocate the vector if needed
369
379
  if (!setup_priority_overrides) {
370
- setup_priority_overrides = std::make_unique<std::vector<std::pair<const Component *, float>>>();
380
+ setup_priority_overrides = std::make_unique<std::vector<ComponentPriorityOverride>>();
371
381
  // Reserve some space to avoid reallocations (most configs have < 10 overrides)
372
382
  setup_priority_overrides->reserve(10);
373
383
  }
374
384
 
375
385
  // Check if this component already has an override
376
- for (auto &pair : *setup_priority_overrides) {
377
- if (pair.first == this) {
378
- pair.second = priority;
386
+ for (auto &entry : *setup_priority_overrides) {
387
+ if (entry.component == this) {
388
+ entry.priority = priority;
379
389
  return;
380
390
  }
381
391
  }
382
392
 
383
393
  // Add new override
384
- setup_priority_overrides->emplace_back(this, priority);
394
+ setup_priority_overrides->emplace_back(ComponentPriorityOverride{this, priority});
385
395
  }
386
396
 
387
397
  bool Component::has_overridden_loop() const {
@@ -168,8 +168,9 @@ class ComponentIterator {
168
168
  UPDATE,
169
169
  #endif
170
170
  MAX,
171
- } state_{IteratorState::NONE};
171
+ };
172
172
  uint16_t at_{0}; // Supports up to 65,535 entities per type
173
+ IteratorState state_{IteratorState::NONE};
173
174
  bool include_internal_{false};
174
175
 
175
176
  template<typename Container>
esphome/core/config.py CHANGED
@@ -136,21 +136,21 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType:
136
136
  return config
137
137
 
138
138
 
139
- def valid_include(value):
139
+ def valid_include(value: str) -> str:
140
140
  # Look for "<...>" includes
141
141
  if value.startswith("<") and value.endswith(">"):
142
142
  return value
143
143
  try:
144
- return cv.directory(value)
144
+ return str(cv.directory(value))
145
145
  except cv.Invalid:
146
146
  pass
147
- value = cv.file_(value)
148
- _, ext = os.path.splitext(value)
147
+ path = cv.file_(value)
148
+ ext = path.suffix
149
149
  if ext not in VALID_INCLUDE_EXTS:
150
150
  raise cv.Invalid(
151
151
  f"Include has invalid file extension {ext} - valid extensions are {', '.join(VALID_INCLUDE_EXTS)}"
152
152
  )
153
- return value
153
+ return str(path)
154
154
 
155
155
 
156
156
  def valid_project_name(value: str):
@@ -311,9 +311,9 @@ def preload_core_config(config, result) -> str:
311
311
  CORE.data[KEY_CORE] = {}
312
312
 
313
313
  if CONF_BUILD_PATH not in conf:
314
- build_path = get_str_env("ESPHOME_BUILD_PATH", "build")
315
- conf[CONF_BUILD_PATH] = os.path.join(build_path, CORE.name)
316
- CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH])
314
+ build_path = Path(get_str_env("ESPHOME_BUILD_PATH", "build"))
315
+ conf[CONF_BUILD_PATH] = str(build_path / CORE.name)
316
+ CORE.build_path = CORE.data_dir / conf[CONF_BUILD_PATH]
317
317
 
318
318
  target_platforms = []
319
319
 
@@ -339,12 +339,12 @@ def preload_core_config(config, result) -> str:
339
339
  return target_platforms[0]
340
340
 
341
341
 
342
- def include_file(path, basename):
343
- parts = basename.split(os.path.sep)
342
+ def include_file(path: Path, basename: Path):
343
+ parts = basename.parts
344
344
  dst = CORE.relative_src_path(*parts)
345
345
  copy_file_if_changed(path, dst)
346
346
 
347
- _, ext = os.path.splitext(path)
347
+ ext = path.suffix
348
348
  if ext in [".h", ".hpp", ".tcc"]:
349
349
  # Header, add include statement
350
350
  cg.add_global(cg.RawStatement(f'#include "{basename}"'))
@@ -377,18 +377,18 @@ async def add_arduino_global_workaround():
377
377
 
378
378
 
379
379
  @coroutine_with_priority(CoroPriority.FINAL)
380
- async def add_includes(includes):
380
+ async def add_includes(includes: list[str]) -> None:
381
381
  # Add includes at the very end, so that the included files can access global variables
382
382
  for include in includes:
383
383
  path = CORE.relative_config_path(include)
384
- if os.path.isdir(path):
384
+ if path.is_dir():
385
385
  # Directory, copy tree
386
386
  for p in walk_files(path):
387
- basename = os.path.relpath(p, os.path.dirname(path))
387
+ basename = p.relative_to(path.parent)
388
388
  include_file(p, basename)
389
389
  else:
390
390
  # Copy file
391
- basename = os.path.basename(path)
391
+ basename = Path(path.name)
392
392
  include_file(path, basename)
393
393
 
394
394
 
esphome/core/defines.h CHANGED
@@ -48,6 +48,7 @@
48
48
  #define USE_LIGHT
49
49
  #define USE_LOCK
50
50
  #define USE_LOGGER
51
+ #define USE_LOGGER_RUNTIME_TAG_LEVELS
51
52
  #define USE_LVGL
52
53
  #define USE_LVGL_ANIMIMG
53
54
  #define USE_LVGL_ARC
@@ -82,6 +83,8 @@
82
83
  #define USE_LVGL_TILEVIEW
83
84
  #define USE_LVGL_TOUCHSCREEN
84
85
  #define USE_MDNS
86
+ #define MDNS_SERVICE_COUNT 3
87
+ #define MDNS_DYNAMIC_TXT_COUNT 3
85
88
  #define USE_MEDIA_PLAYER
86
89
  #define USE_NEXTION_TFT_UPLOAD
87
90
  #define USE_NUMBER
@@ -100,6 +103,7 @@
100
103
  #define USE_UART_DEBUGGER
101
104
  #define USE_UPDATE
102
105
  #define USE_VALVE
106
+ #define USE_ZWAVE_PROXY
103
107
 
104
108
  // Feature flags which do not work for zephyr
105
109
  #ifndef USE_ZEPHYR
@@ -109,19 +113,26 @@
109
113
  #define USE_API
110
114
  #define USE_API_CLIENT_CONNECTED_TRIGGER
111
115
  #define USE_API_CLIENT_DISCONNECTED_TRIGGER
116
+ #define USE_API_HOMEASSISTANT_ACTION_RESPONSES
117
+ #define USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
112
118
  #define USE_API_HOMEASSISTANT_SERVICES
113
119
  #define USE_API_HOMEASSISTANT_STATES
114
120
  #define USE_API_NOISE
115
121
  #define USE_API_PLAINTEXT
116
122
  #define USE_API_SERVICES
123
+ #define API_MAX_SEND_QUEUE 8
117
124
  #define USE_MD5
125
+ #define USE_SHA256
118
126
  #define USE_MQTT
119
127
  #define USE_NETWORK
120
128
  #define USE_ONLINE_IMAGE_BMP_SUPPORT
121
129
  #define USE_ONLINE_IMAGE_PNG_SUPPORT
122
130
  #define USE_ONLINE_IMAGE_JPEG_SUPPORT
123
131
  #define USE_OTA
132
+ #define USE_OTA_MD5
124
133
  #define USE_OTA_PASSWORD
134
+ #define USE_OTA_SHA256
135
+ #define ALLOW_OTA_DOWNGRADE_MD5
125
136
  #define USE_OTA_STATE_CALLBACK
126
137
  #define USE_OTA_VERSION 2
127
138
  #define USE_TIME_TIMEZONE
@@ -151,11 +162,20 @@
151
162
  #define BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE 16
152
163
  #define USE_CAPTIVE_PORTAL
153
164
  #define USE_ESP32_BLE
165
+ #define USE_ESP32_BLE_MAX_CONNECTIONS 3
154
166
  #define USE_ESP32_BLE_CLIENT
155
167
  #define USE_ESP32_BLE_DEVICE
156
168
  #define USE_ESP32_BLE_SERVER
157
169
  #define USE_ESP32_BLE_UUID
158
170
  #define USE_ESP32_BLE_ADVERTISING
171
+ #define USE_ESP32_BLE_SERVER_SET_VALUE_ACTION
172
+ #define USE_ESP32_BLE_SERVER_DESCRIPTOR_SET_VALUE_ACTION
173
+ #define USE_ESP32_BLE_SERVER_NOTIFY_ACTION
174
+ #define USE_ESP32_BLE_SERVER_CHARACTERISTIC_ON_WRITE
175
+ #define USE_ESP32_BLE_SERVER_DESCRIPTOR_ON_WRITE
176
+ #define USE_ESP32_BLE_SERVER_ON_CONNECT
177
+ #define USE_ESP32_BLE_SERVER_ON_DISCONNECT
178
+ #define USE_ESP32_CAMERA_JPEG_ENCODER
159
179
  #define USE_I2C
160
180
  #define USE_IMPROV
161
181
  #define USE_MICROPHONE
@@ -171,6 +191,7 @@
171
191
  #define USE_WEBSERVER_PORT 80 // NOLINT
172
192
  #define USE_WEBSERVER_SORTING
173
193
  #define USE_WIFI_11KV_SUPPORT
194
+ #define USB_HOST_MAX_REQUESTS 16
174
195
 
175
196
  #ifdef USE_ARDUINO
176
197
  #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 2, 1)
@@ -246,12 +246,15 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy
246
246
  "\n to distinguish them"
247
247
  )
248
248
 
249
- raise cv.Invalid(
250
- f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. "
251
- f"{conflict_msg}. "
252
- "Each entity on a device must have a unique name within its platform."
253
- f"{sanitized_msg}"
254
- )
249
+ # Skip duplicate entity name validation when testing_mode is enabled
250
+ # This flag is used for grouped component testing
251
+ if not CORE.testing_mode:
252
+ raise cv.Invalid(
253
+ f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. "
254
+ f"{conflict_msg}. "
255
+ "Each entity on a device must have a unique name within its platform."
256
+ f"{sanitized_msg}"
257
+ )
255
258
 
256
259
  # Store metadata about this entity
257
260
  entity_metadata: EntityMetadata = {
@@ -0,0 +1,56 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <cstddef>
5
+ #include <cstring>
6
+ #include "esphome/core/helpers.h"
7
+
8
+ namespace esphome {
9
+
10
+ /// Base class for hash algorithms
11
+ class HashBase {
12
+ public:
13
+ virtual ~HashBase() = default;
14
+
15
+ /// Initialize a new hash computation
16
+ virtual void init() = 0;
17
+
18
+ /// Add bytes of data for the hash
19
+ virtual void add(const uint8_t *data, size_t len) = 0;
20
+ void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
21
+
22
+ /// Compute the hash based on provided data
23
+ virtual void calculate() = 0;
24
+
25
+ /// Retrieve the hash as bytes
26
+ void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); }
27
+
28
+ /// Retrieve the hash as hex characters
29
+ void get_hex(char *output) {
30
+ for (size_t i = 0; i < this->get_size(); i++) {
31
+ uint8_t byte = this->digest_[i];
32
+ output[i * 2] = format_hex_char(byte >> 4);
33
+ output[i * 2 + 1] = format_hex_char(byte & 0x0F);
34
+ }
35
+ }
36
+
37
+ /// Compare the hash against a provided byte-encoded hash
38
+ bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; }
39
+
40
+ /// Compare the hash against a provided hex-encoded hash
41
+ bool equals_hex(const char *expected) {
42
+ uint8_t parsed[32]; // Fixed size for max hash (SHA256 = 32 bytes)
43
+ if (!parse_hex(expected, parsed, this->get_size())) {
44
+ return false;
45
+ }
46
+ return this->equals_bytes(parsed);
47
+ }
48
+
49
+ /// Get the size of the hash in bytes (16 for MD5, 32 for SHA256)
50
+ virtual size_t get_size() const = 0;
51
+
52
+ protected:
53
+ uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes
54
+ };
55
+
56
+ } // namespace esphome
esphome/core/helpers.cpp CHANGED
@@ -3,6 +3,7 @@
3
3
  #include "esphome/core/defines.h"
4
4
  #include "esphome/core/hal.h"
5
5
  #include "esphome/core/log.h"
6
+ #include "esphome/core/string_ref.h"
6
7
 
7
8
  #include <strings.h>
8
9
  #include <algorithm>
@@ -348,17 +349,34 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
348
349
  return PARSE_NONE;
349
350
  }
350
351
 
351
- std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
352
+ static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_decimals) {
352
353
  if (accuracy_decimals < 0) {
353
354
  auto multiplier = powf(10.0f, accuracy_decimals);
354
355
  value = roundf(value * multiplier) / multiplier;
355
356
  accuracy_decimals = 0;
356
357
  }
358
+ }
359
+
360
+ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
361
+ normalize_accuracy_decimals(value, accuracy_decimals);
357
362
  char tmp[32]; // should be enough, but we should maybe improve this at some point.
358
363
  snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value);
359
364
  return std::string(tmp);
360
365
  }
361
366
 
367
+ std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement) {
368
+ normalize_accuracy_decimals(value, accuracy_decimals);
369
+ // Buffer sized for float (up to ~15 chars) + space + typical UOM (usually <20 chars like "μS/cm")
370
+ // snprintf truncates safely if exceeded, though ESPHome UOMs are typically short
371
+ char tmp[64];
372
+ if (unit_of_measurement.empty()) {
373
+ snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value);
374
+ } else {
375
+ snprintf(tmp, sizeof(tmp), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str());
376
+ }
377
+ return std::string(tmp);
378
+ }
379
+
362
380
  int8_t step_to_accuracy_decimals(float step) {
363
381
  // use printf %g to find number of digits based on temperature step
364
382
  char buf[32];
@@ -613,8 +631,6 @@ bool mac_address_is_valid(const uint8_t *mac) {
613
631
  if (mac[i] != 0) {
614
632
  is_all_zeros = false;
615
633
  }
616
- }
617
- for (uint8_t i = 0; i < 6; i++) {
618
634
  if (mac[i] != 0xFF) {
619
635
  is_all_ones = false;
620
636
  }