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
@@ -17,6 +17,13 @@ static const char *const TAG = "esp32_improv.component";
17
17
  static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
18
18
  static constexpr uint16_t STOP_ADVERTISING_DELAY =
19
19
  10000; // Delay (ms) before stopping service to allow BLE clients to read the final state
20
+ static constexpr uint16_t NAME_ADVERTISING_INTERVAL = 60000; // Advertise name every 60 seconds
21
+ static constexpr uint16_t NAME_ADVERTISING_DURATION = 1000; // Advertise name for 1 second
22
+
23
+ // Improv service data constants
24
+ static constexpr uint8_t IMPROV_SERVICE_DATA_SIZE = 8;
25
+ static constexpr uint8_t IMPROV_PROTOCOL_ID_1 = 0x77; // 'P' << 1 | 'R' >> 7
26
+ static constexpr uint8_t IMPROV_PROTOCOL_ID_2 = 0x46; // 'I' << 1 | 'M' >> 7
20
27
 
21
28
  ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
22
29
 
@@ -31,8 +38,7 @@ void ESP32ImprovComponent::setup() {
31
38
  });
32
39
  }
33
40
  #endif
34
- global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT,
35
- [this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
41
+ global_ble_server->on_disconnect([this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
36
42
 
37
43
  // Start with loop disabled - will be enabled by start() when needed
38
44
  this->disable_loop();
@@ -50,12 +56,11 @@ void ESP32ImprovComponent::setup_characteristics() {
50
56
  this->error_->add_descriptor(error_descriptor);
51
57
 
52
58
  this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
53
- this->rpc_->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::on(
54
- BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &data, uint16_t id) {
55
- if (!data.empty()) {
56
- this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
57
- }
58
- });
59
+ this->rpc_->on_write([this](std::span<const uint8_t> data, uint16_t id) {
60
+ if (!data.empty()) {
61
+ this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
62
+ }
63
+ });
59
64
  BLEDescriptor *rpc_descriptor = new BLE2902();
60
65
  this->rpc_->add_descriptor(rpc_descriptor);
61
66
 
@@ -99,6 +104,11 @@ void ESP32ImprovComponent::loop() {
99
104
  this->process_incoming_data_();
100
105
  uint32_t now = App.get_loop_component_start_time();
101
106
 
107
+ // Check if we need to update advertising type
108
+ if (this->state_ != improv::STATE_STOPPED && this->state_ != improv::STATE_PROVISIONED) {
109
+ this->update_advertising_type_();
110
+ }
111
+
102
112
  switch (this->state_) {
103
113
  case improv::STATE_STOPPED:
104
114
  this->set_status_indicator_state_(false);
@@ -107,9 +117,15 @@ void ESP32ImprovComponent::loop() {
107
117
  if (this->service_->is_created()) {
108
118
  this->service_->start();
109
119
  } else if (this->service_->is_running()) {
120
+ // Start by advertising the device name first BEFORE setting any state
121
+ ESP_LOGV(TAG, "Starting with device name advertising");
122
+ this->advertising_device_name_ = true;
123
+ this->last_name_adv_time_ = App.get_loop_component_start_time();
124
+ esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>{}, true);
110
125
  esp32_ble::global_ble->advertising_start();
111
126
 
112
- this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
127
+ // Set initial state based on whether we have an authorizer
128
+ this->set_state_(this->get_initial_state_(), false);
113
129
  this->set_error_(improv::ERROR_NONE);
114
130
  ESP_LOGD(TAG, "Service started!");
115
131
  }
@@ -120,54 +136,33 @@ void ESP32ImprovComponent::loop() {
120
136
  if (this->authorizer_ == nullptr ||
121
137
  (this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) {
122
138
  this->set_state_(improv::STATE_AUTHORIZED);
123
- } else
124
- #else
125
- { this->set_state_(improv::STATE_AUTHORIZED); }
126
- #endif
127
- {
139
+ } else {
128
140
  if (!this->check_identify_())
129
141
  this->set_status_indicator_state_(true);
130
142
  }
143
+ #else
144
+ this->set_state_(improv::STATE_AUTHORIZED);
145
+ #endif
146
+ this->check_wifi_connection_();
131
147
  break;
132
148
  }
133
149
  case improv::STATE_AUTHORIZED: {
134
150
  #ifdef USE_BINARY_SENSOR
135
- if (this->authorizer_ != nullptr) {
136
- if (now - this->authorized_start_ > this->authorized_duration_) {
137
- ESP_LOGD(TAG, "Authorization timeout");
138
- this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
139
- return;
140
- }
151
+ if (this->authorizer_ != nullptr && now - this->authorized_start_ > this->authorized_duration_) {
152
+ ESP_LOGD(TAG, "Authorization timeout");
153
+ this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
154
+ return;
141
155
  }
142
156
  #endif
143
157
  if (!this->check_identify_()) {
144
158
  this->set_status_indicator_state_((now % 1000) < 500);
145
159
  }
160
+ this->check_wifi_connection_();
146
161
  break;
147
162
  }
148
163
  case improv::STATE_PROVISIONING: {
149
164
  this->set_status_indicator_state_((now % 200) < 100);
150
- if (wifi::global_wifi_component->is_connected()) {
151
- wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
152
- this->connecting_sta_.get_password());
153
- this->connecting_sta_ = {};
154
- this->cancel_timeout("wifi-connect-timeout");
155
- this->set_state_(improv::STATE_PROVISIONED);
156
-
157
- std::vector<std::string> urls = {ESPHOME_MY_LINK};
158
- #ifdef USE_WEBSERVER
159
- for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
160
- if (ip.is_ip4()) {
161
- std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
162
- urls.push_back(webserver_url);
163
- break;
164
- }
165
- }
166
- #endif
167
- std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
168
- this->send_response_(data);
169
- this->stop();
170
- }
165
+ this->check_wifi_connection_();
171
166
  break;
172
167
  }
173
168
  case improv::STATE_PROVISIONED: {
@@ -226,12 +221,15 @@ bool ESP32ImprovComponent::check_identify_() {
226
221
  return identify;
227
222
  }
228
223
 
229
- void ESP32ImprovComponent::set_state_(improv::State state) {
230
- #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
231
- if (this->state_ != state) {
232
- ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_,
233
- this->state_to_string_(state), state);
224
+ void ESP32ImprovComponent::set_state_(improv::State state, bool update_advertising) {
225
+ // Skip if state hasn't changed
226
+ if (this->state_ == state) {
227
+ return;
234
228
  }
229
+
230
+ #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
231
+ ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_,
232
+ this->state_to_string_(state), state);
235
233
  #endif
236
234
  this->state_ = state;
237
235
  if (this->status_ != nullptr && (this->status_->get_value().empty() || this->status_->get_value()[0] != state)) {
@@ -243,25 +241,13 @@ void ESP32ImprovComponent::set_state_(improv::State state) {
243
241
  // STATE_STOPPED (0x00) is internal only and not part of the Improv spec.
244
242
  // Advertising 0x00 causes undefined behavior in some clients and makes them
245
243
  // repeatedly connect trying to determine the actual state.
246
- if (state != improv::STATE_STOPPED) {
247
- std::vector<uint8_t> service_data(8, 0);
248
- service_data[0] = 0x77; // PR
249
- service_data[1] = 0x46; // IM
250
- service_data[2] = static_cast<uint8_t>(state);
251
-
252
- uint8_t capabilities = 0x00;
253
- #ifdef USE_OUTPUT
254
- if (this->status_indicator_ != nullptr)
255
- capabilities |= improv::CAPABILITY_IDENTIFY;
256
- #endif
257
-
258
- service_data[3] = capabilities;
259
- service_data[4] = 0x00; // Reserved
260
- service_data[5] = 0x00; // Reserved
261
- service_data[6] = 0x00; // Reserved
262
- service_data[7] = 0x00; // Reserved
263
-
264
- esp32_ble::global_ble->advertising_set_service_data(service_data);
244
+ if (state != improv::STATE_STOPPED && update_advertising) {
245
+ // State change always overrides name advertising and resets the timer
246
+ this->advertising_device_name_ = false;
247
+ // Reset the timer so we wait another 60 seconds before advertising name
248
+ this->last_name_adv_time_ = App.get_loop_component_start_time();
249
+ // Advertise the new state via service data
250
+ this->advertise_service_data_();
265
251
  }
266
252
  #ifdef USE_ESP32_IMPROV_STATE_CALLBACK
267
253
  this->state_callback_.call(this->state_, this->error_state_);
@@ -388,6 +374,90 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() {
388
374
  wifi::global_wifi_component->clear_sta();
389
375
  }
390
376
 
377
+ void ESP32ImprovComponent::check_wifi_connection_() {
378
+ if (!wifi::global_wifi_component->is_connected()) {
379
+ return;
380
+ }
381
+
382
+ if (this->state_ == improv::STATE_PROVISIONING) {
383
+ wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(), this->connecting_sta_.get_password());
384
+ this->connecting_sta_ = {};
385
+ this->cancel_timeout("wifi-connect-timeout");
386
+
387
+ std::vector<std::string> urls = {ESPHOME_MY_LINK};
388
+ #ifdef USE_WEBSERVER
389
+ for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
390
+ if (ip.is_ip4()) {
391
+ std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
392
+ urls.push_back(webserver_url);
393
+ break;
394
+ }
395
+ }
396
+ #endif
397
+ std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
398
+ this->send_response_(data);
399
+ } else if (this->is_active() && this->state_ != improv::STATE_PROVISIONED) {
400
+ ESP_LOGD(TAG, "WiFi provisioned externally");
401
+ }
402
+
403
+ this->set_state_(improv::STATE_PROVISIONED);
404
+ this->stop();
405
+ }
406
+
407
+ void ESP32ImprovComponent::advertise_service_data_() {
408
+ uint8_t service_data[IMPROV_SERVICE_DATA_SIZE] = {};
409
+ service_data[0] = IMPROV_PROTOCOL_ID_1; // PR
410
+ service_data[1] = IMPROV_PROTOCOL_ID_2; // IM
411
+ service_data[2] = static_cast<uint8_t>(this->state_);
412
+
413
+ uint8_t capabilities = 0x00;
414
+ #ifdef USE_OUTPUT
415
+ if (this->status_indicator_ != nullptr)
416
+ capabilities |= improv::CAPABILITY_IDENTIFY;
417
+ #endif
418
+
419
+ service_data[3] = capabilities;
420
+ // service_data[4-7] are already 0 (Reserved)
421
+
422
+ // Atomically set service data and disable name in advertising
423
+ esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>(service_data), false);
424
+ }
425
+
426
+ void ESP32ImprovComponent::update_advertising_type_() {
427
+ uint32_t now = App.get_loop_component_start_time();
428
+
429
+ // If we're advertising the device name and it's been more than NAME_ADVERTISING_DURATION, switch back to service data
430
+ if (this->advertising_device_name_) {
431
+ if (now - this->last_name_adv_time_ >= NAME_ADVERTISING_DURATION) {
432
+ ESP_LOGV(TAG, "Switching back to service data advertising");
433
+ this->advertising_device_name_ = false;
434
+ // Restore service data advertising
435
+ this->advertise_service_data_();
436
+ }
437
+ return;
438
+ }
439
+
440
+ // Check if it's time to advertise the device name (every NAME_ADVERTISING_INTERVAL)
441
+ if (now - this->last_name_adv_time_ >= NAME_ADVERTISING_INTERVAL) {
442
+ ESP_LOGV(TAG, "Switching to device name advertising");
443
+ this->advertising_device_name_ = true;
444
+ this->last_name_adv_time_ = now;
445
+
446
+ // Atomically clear service data and enable name in advertising data
447
+ esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>{}, true);
448
+ }
449
+ }
450
+
451
+ improv::State ESP32ImprovComponent::get_initial_state_() const {
452
+ #ifdef USE_BINARY_SENSOR
453
+ // If we have an authorizer, start in awaiting authorization state
454
+ return this->authorizer_ == nullptr ? improv::STATE_AUTHORIZED : improv::STATE_AWAITING_AUTHORIZATION;
455
+ #else
456
+ // No binary_sensor support = no authorizer possible, start as authorized
457
+ return improv::STATE_AUTHORIZED;
458
+ #endif
459
+ }
460
+
391
461
  ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
392
462
 
393
463
  } // namespace esp32_improv
@@ -100,14 +100,20 @@ class ESP32ImprovComponent : public Component {
100
100
  #endif
101
101
 
102
102
  bool status_indicator_state_{false};
103
+ uint32_t last_name_adv_time_{0};
104
+ bool advertising_device_name_{false};
103
105
  void set_status_indicator_state_(bool state);
106
+ void update_advertising_type_();
104
107
 
105
- void set_state_(improv::State state);
108
+ void set_state_(improv::State state, bool update_advertising = true);
106
109
  void set_error_(improv::Error error);
110
+ improv::State get_initial_state_() const;
107
111
  void send_response_(std::vector<uint8_t> &response);
108
112
  void process_incoming_data_();
109
113
  void on_wifi_connect_timeout_();
114
+ void check_wifi_connection_();
110
115
  bool check_identify_();
116
+ void advertise_service_data_();
111
117
  #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
112
118
  const char *state_to_string_(improv::State state);
113
119
  #endif
@@ -35,7 +35,7 @@ static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size
35
35
  if (symbols_free < RMT_SYMBOLS_PER_BYTE) {
36
36
  return 0;
37
37
  }
38
- for (int32_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) {
38
+ for (size_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) {
39
39
  if (bytes[index] & (1 << (7 - i))) {
40
40
  symbols[i] = params->bit1;
41
41
  } else {
@@ -1,5 +1,5 @@
1
1
  import logging
2
- import os
2
+ from pathlib import Path
3
3
 
4
4
  import esphome.codegen as cg
5
5
  import esphome.config_validation as cv
@@ -259,8 +259,8 @@ async def to_code(config):
259
259
 
260
260
  # Called by writer.py
261
261
  def copy_files():
262
- dir = os.path.dirname(__file__)
263
- post_build_file = os.path.join(dir, "post_build.py.script")
262
+ dir = Path(__file__).parent
263
+ post_build_file = dir / "post_build.py.script"
264
264
  copy_file_if_changed(
265
265
  post_build_file,
266
266
  CORE.relative_build_path("post_build.py"),
@@ -16,7 +16,7 @@ from esphome.const import (
16
16
  CONF_SAFE_MODE,
17
17
  CONF_VERSION,
18
18
  )
19
- from esphome.core import coroutine_with_priority
19
+ from esphome.core import CORE, coroutine_with_priority
20
20
  from esphome.coroutine import CoroPriority
21
21
  import esphome.final_validate as fv
22
22
 
@@ -24,9 +24,22 @@ _LOGGER = logging.getLogger(__name__)
24
24
 
25
25
 
26
26
  CODEOWNERS = ["@esphome/core"]
27
- AUTO_LOAD = ["md5", "socket"]
28
27
  DEPENDENCIES = ["network"]
29
28
 
29
+
30
+ def supports_sha256() -> bool:
31
+ """Check if the current platform supports SHA256 for OTA authentication."""
32
+ return bool(CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny)
33
+
34
+
35
+ def AUTO_LOAD() -> list[str]:
36
+ """Conditionally auto-load sha256 only on platforms that support it."""
37
+ base_components = ["md5", "socket"]
38
+ if supports_sha256():
39
+ return base_components + ["sha256"]
40
+ return base_components
41
+
42
+
30
43
  esphome = cg.esphome_ns.namespace("esphome")
31
44
  ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent)
32
45
 
@@ -126,9 +139,15 @@ FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
126
139
  async def to_code(config):
127
140
  var = cg.new_Pvariable(config[CONF_ID])
128
141
  cg.add(var.set_port(config[CONF_PORT]))
142
+
129
143
  if CONF_PASSWORD in config:
130
144
  cg.add(var.set_auth_password(config[CONF_PASSWORD]))
131
145
  cg.add_define("USE_OTA_PASSWORD")
146
+ # Only include hash algorithms when password is configured
147
+ cg.add_define("USE_OTA_MD5")
148
+ # Only include SHA256 support on platforms that have it
149
+ if supports_sha256():
150
+ cg.add_define("USE_OTA_SHA256")
132
151
  cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
133
152
 
134
153
  await cg.register_component(var, config)