esphome 2025.9.2__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 (344) 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 +167 -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 +78 -11
  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/sim800l/sim800l.cpp +8 -4
  215. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  216. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  217. esphome/components/spi/__init__.py +0 -3
  218. esphome/components/split_buffer/__init__.py +5 -0
  219. esphome/components/split_buffer/split_buffer.cpp +133 -0
  220. esphome/components/split_buffer/split_buffer.h +40 -0
  221. esphome/components/sps30/sps30.cpp +14 -10
  222. esphome/components/sps30/sps30.h +2 -0
  223. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  224. esphome/components/st7789v/st7789v.cpp +3 -2
  225. esphome/components/statsd/statsd.cpp +1 -1
  226. esphome/components/substitutions/__init__.py +3 -1
  227. esphome/components/substitutions/jinja.py +13 -3
  228. esphome/components/sx126x/__init__.py +16 -0
  229. esphome/components/sx126x/sx126x.cpp +15 -1
  230. esphome/components/sx126x/sx126x.h +9 -1
  231. esphome/components/sx126x/sx126x_reg.h +2 -0
  232. esphome/components/text_sensor/text_sensor.cpp +16 -0
  233. esphome/components/text_sensor/text_sensor.h +3 -10
  234. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  235. esphome/components/tuya/select/tuya_select.cpp +1 -1
  236. esphome/components/tuya/tuya.cpp +29 -4
  237. esphome/components/uart/__init__.py +36 -26
  238. esphome/components/uart/uart.h +6 -0
  239. esphome/components/uart/uart_component.cpp +8 -0
  240. esphome/components/uart/uart_component.h +28 -0
  241. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  242. esphome/components/uart/uart_component_esp_idf.h +5 -2
  243. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  244. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  245. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  246. esphome/components/usb_host/__init__.py +2 -1
  247. esphome/components/usb_host/usb_host.h +82 -13
  248. esphome/components/usb_host/usb_host_client.cpp +180 -24
  249. esphome/components/usb_host/usb_host_component.cpp +1 -1
  250. esphome/components/usb_uart/__init__.py +0 -1
  251. esphome/components/usb_uart/ch34x.cpp +4 -4
  252. esphome/components/usb_uart/cp210x.cpp +3 -3
  253. esphome/components/usb_uart/usb_uart.cpp +88 -32
  254. esphome/components/usb_uart/usb_uart.h +30 -6
  255. esphome/components/valve/valve.cpp +1 -0
  256. esphome/components/veml7700/veml7700.cpp +7 -6
  257. esphome/components/version/version_text_sensor.cpp +2 -1
  258. esphome/components/voice_assistant/voice_assistant.cpp +3 -3
  259. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  260. esphome/components/web_server/list_entities.cpp +3 -4
  261. esphome/components/web_server/list_entities.h +8 -10
  262. esphome/components/web_server/ota/__init__.py +1 -1
  263. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  264. esphome/components/web_server/web_server.cpp +509 -404
  265. esphome/components/web_server/web_server.h +5 -6
  266. esphome/components/web_server/web_server_v1.cpp +21 -19
  267. esphome/components/web_server_base/__init__.py +5 -2
  268. esphome/components/web_server_base/web_server_base.h +27 -7
  269. esphome/components/web_server_idf/__init__.py +1 -1
  270. esphome/components/web_server_idf/multipart.cpp +2 -2
  271. esphome/components/web_server_idf/multipart.h +2 -2
  272. esphome/components/web_server_idf/utils.cpp +2 -2
  273. esphome/components/web_server_idf/utils.h +2 -2
  274. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  275. esphome/components/web_server_idf/web_server_idf.h +12 -10
  276. esphome/components/wifi/__init__.py +13 -11
  277. esphome/components/wifi/wifi_component.cpp +73 -56
  278. esphome/components/wifi/wifi_component.h +4 -4
  279. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  280. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  281. esphome/components/wireguard/__init__.py +1 -1
  282. esphome/components/wts01/__init__.py +0 -0
  283. esphome/components/wts01/sensor.py +41 -0
  284. esphome/components/wts01/wts01.cpp +91 -0
  285. esphome/components/wts01/wts01.h +27 -0
  286. esphome/components/zephyr/__init__.py +5 -5
  287. esphome/components/zwave_proxy/__init__.py +43 -0
  288. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  289. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  290. esphome/config.py +79 -24
  291. esphome/config_validation.py +13 -15
  292. esphome/const.py +9 -2
  293. esphome/core/__init__.py +31 -22
  294. esphome/core/component.cpp +28 -18
  295. esphome/core/component_iterator.h +2 -1
  296. esphome/core/config.py +15 -15
  297. esphome/core/defines.h +19 -0
  298. esphome/core/hash_base.h +56 -0
  299. esphome/core/helpers.cpp +19 -3
  300. esphome/core/helpers.h +26 -0
  301. esphome/core/scheduler.cpp +5 -21
  302. esphome/core/scheduler.h +19 -8
  303. esphome/core/string_ref.h +1 -1
  304. esphome/core/time.cpp +5 -5
  305. esphome/cpp_generator.py +4 -29
  306. esphome/dashboard/const.py +21 -4
  307. esphome/dashboard/core.py +10 -8
  308. esphome/dashboard/dns.py +15 -0
  309. esphome/dashboard/entries.py +15 -21
  310. esphome/dashboard/models.py +76 -0
  311. esphome/dashboard/settings.py +7 -7
  312. esphome/dashboard/status/mdns.py +46 -2
  313. esphome/dashboard/web_server.py +367 -93
  314. esphome/espota2.py +111 -31
  315. esphome/external_files.py +6 -7
  316. esphome/git.py +8 -0
  317. esphome/helpers.py +124 -77
  318. esphome/loader.py +8 -9
  319. esphome/platformio_api.py +25 -18
  320. esphome/storage_json.py +26 -21
  321. esphome/types.py +30 -2
  322. esphome/util.py +32 -16
  323. esphome/vscode.py +8 -8
  324. esphome/wizard.py +10 -10
  325. esphome/writer.py +50 -15
  326. esphome/yaml_util.py +37 -31
  327. esphome/zeroconf.py +12 -3
  328. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/METADATA +11 -11
  329. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/RECORD +333 -313
  330. esphome/components/event_emitter/__init__.py +0 -5
  331. esphome/components/event_emitter/event_emitter.cpp +0 -14
  332. esphome/components/event_emitter/event_emitter.h +0 -63
  333. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  334. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  335. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  336. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  337. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  338. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  339. esphome/core/string_ref.cpp +0 -12
  340. esphome/dashboard/util/file.py +0 -63
  341. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/WHEEL +0 -0
  342. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/entry_points.txt +0 -0
  343. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/licenses/LICENSE +0 -0
  344. {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable, MutableMapping
4
3
  import logging
5
- from typing import Any
6
4
 
7
5
  from esphome import automation
8
6
  import esphome.codegen as cg
9
7
  from esphome.components import esp32_ble
10
8
  from esphome.components.esp32 import add_idf_sdkconfig_option
11
9
  from esphome.components.esp32_ble import (
10
+ IDF_MAX_CONNECTIONS,
12
11
  BTLoggers,
13
12
  bt_uuid,
14
13
  bt_uuid16_format,
@@ -24,6 +23,7 @@ from esphome.const import (
24
23
  CONF_INTERVAL,
25
24
  CONF_MAC_ADDRESS,
26
25
  CONF_MANUFACTURER_ID,
26
+ CONF_MAX_CONNECTIONS,
27
27
  CONF_ON_BLE_ADVERTISE,
28
28
  CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
29
29
  CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
@@ -38,19 +38,12 @@ AUTO_LOAD = ["esp32_ble"]
38
38
  DEPENDENCIES = ["esp32"]
39
39
  CODEOWNERS = ["@bdraco"]
40
40
 
41
- KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker"
42
- KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
43
-
44
- CONF_MAX_CONNECTIONS = "max_connections"
45
41
  CONF_ESP32_BLE_ID = "esp32_ble_id"
46
42
  CONF_SCAN_PARAMETERS = "scan_parameters"
47
43
  CONF_WINDOW = "window"
48
44
  CONF_ON_SCAN_END = "on_scan_end"
49
45
  CONF_SOFTWARE_COEXISTENCE = "software_coexistence"
50
46
 
51
- DEFAULT_MAX_CONNECTIONS = 3
52
- IDF_MAX_CONNECTIONS = 9
53
-
54
47
  _LOGGER = logging.getLogger(__name__)
55
48
 
56
49
 
@@ -128,6 +121,15 @@ def validate_scan_parameters(config):
128
121
  return config
129
122
 
130
123
 
124
+ def validate_max_connections_deprecated(config: ConfigType) -> ConfigType:
125
+ if CONF_MAX_CONNECTIONS in config:
126
+ _LOGGER.warning(
127
+ "The 'max_connections' option in 'esp32_ble_tracker' is deprecated. "
128
+ "Please move it to the 'esp32_ble' component instead."
129
+ )
130
+ return config
131
+
132
+
131
133
  def as_hex(value):
132
134
  return cg.RawExpression(f"0x{value}ULL")
133
135
 
@@ -150,29 +152,13 @@ def as_reversed_hex_array(value):
150
152
  )
151
153
 
152
154
 
153
- def max_connections() -> int:
154
- return IDF_MAX_CONNECTIONS if CORE.using_esp_idf else DEFAULT_MAX_CONNECTIONS
155
-
156
-
157
- def consume_connection_slots(
158
- value: int, consumer: str
159
- ) -> Callable[[MutableMapping], MutableMapping]:
160
- def _consume_connection_slots(config: MutableMapping) -> MutableMapping:
161
- data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE_TRACKER, {})
162
- slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, [])
163
- slots.extend([consumer] * value)
164
- return config
165
-
166
- return _consume_connection_slots
167
-
168
-
169
155
  CONFIG_SCHEMA = cv.All(
170
156
  cv.Schema(
171
157
  {
172
158
  cv.GenerateID(): cv.declare_id(ESP32BLETracker),
173
159
  cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
174
- cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All(
175
- cv.positive_int, cv.Range(min=0, max=max_connections())
160
+ cv.Optional(CONF_MAX_CONNECTIONS): cv.All(
161
+ cv.positive_int, cv.Range(min=0, max=IDF_MAX_CONNECTIONS)
176
162
  ),
177
163
  cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
178
164
  cv.Schema(
@@ -228,49 +214,11 @@ CONFIG_SCHEMA = cv.All(
228
214
  cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool,
229
215
  }
230
216
  ).extend(cv.COMPONENT_SCHEMA),
217
+ validate_max_connections_deprecated,
231
218
  )
232
219
 
233
220
 
234
- def validate_remaining_connections(config):
235
- data: dict[str, Any] = CORE.data.get(KEY_ESP32_BLE_TRACKER, {})
236
- slots: list[str] = data.get(KEY_USED_CONNECTION_SLOTS, [])
237
- used_slots = len(slots)
238
- if used_slots <= config[CONF_MAX_CONNECTIONS]:
239
- return config
240
- slot_users = ", ".join(slots)
241
- hard_limit = max_connections()
242
-
243
- if used_slots < hard_limit:
244
- _LOGGER.warning(
245
- "esp32_ble_tracker exceeded `%s`: components attempted to consume %d "
246
- "connection slot(s) out of available configured maximum %d connection "
247
- "slot(s); The system automatically increased `%s` to %d to match the "
248
- "number of used connection slot(s) by components: %s.",
249
- CONF_MAX_CONNECTIONS,
250
- used_slots,
251
- config[CONF_MAX_CONNECTIONS],
252
- CONF_MAX_CONNECTIONS,
253
- used_slots,
254
- slot_users,
255
- )
256
- config[CONF_MAX_CONNECTIONS] = used_slots
257
- return config
258
-
259
- msg = (
260
- f"esp32_ble_tracker exceeded `{CONF_MAX_CONNECTIONS}`: "
261
- f"components attempted to consume {used_slots} connection slot(s) "
262
- f"out of available configured maximum {config[CONF_MAX_CONNECTIONS]} "
263
- f"connection slot(s); Decrease the number of BLE clients ({slot_users})"
264
- )
265
- if config[CONF_MAX_CONNECTIONS] < hard_limit:
266
- msg += f" or increase {CONF_MAX_CONNECTIONS}` to {used_slots}"
267
- msg += f" to stay under the {hard_limit} connection slot(s) limit."
268
- raise cv.Invalid(msg)
269
-
270
-
271
- FINAL_VALIDATE_SCHEMA = cv.All(
272
- validate_remaining_connections, esp32_ble.validate_variant
273
- )
221
+ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
274
222
 
275
223
  ESP_BLE_DEVICE_SCHEMA = cv.Schema(
276
224
  {
@@ -342,19 +290,16 @@ async def to_code(config):
342
290
  trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
343
291
  await automation.build_automation(trigger, [], conf)
344
292
 
345
- if CORE.using_esp_idf:
346
- add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
347
- if config.get(CONF_SOFTWARE_COEXISTENCE):
348
- add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
349
- # https://github.com/espressif/esp-idf/issues/4101
350
- # https://github.com/espressif/esp-idf/issues/2503
351
- # Match arduino CONFIG_BTU_TASK_STACK_SIZE
352
- # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
353
- add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
354
- add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
355
- add_idf_sdkconfig_option(
356
- "CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
357
- )
293
+ add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
294
+ if config.get(CONF_SOFTWARE_COEXISTENCE):
295
+ add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
296
+ # https://github.com/espressif/esp-idf/issues/4101
297
+ # https://github.com/espressif/esp-idf/issues/2503
298
+ # Match arduino CONFIG_BTU_TASK_STACK_SIZE
299
+ # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
300
+ add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
301
+ # Note: CONFIG_BT_ACL_CONNECTIONS and CONFIG_BTDM_CTRL_BLE_MAX_CONN are now
302
+ # configured in esp32_ble component based on max_connections setting
358
303
 
359
304
  cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
360
305
  cg.add_define("USE_ESP32_BLE_CLIENT")
@@ -51,8 +51,6 @@ const char *client_state_to_string(ClientState state) {
51
51
  return "IDLE";
52
52
  case ClientState::DISCOVERED:
53
53
  return "DISCOVERED";
54
- case ClientState::READY_TO_CONNECT:
55
- return "READY_TO_CONNECT";
56
54
  case ClientState::CONNECTING:
57
55
  return "CONNECTING";
58
56
  case ClientState::CONNECTED:
@@ -297,7 +295,7 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
297
295
  void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
298
296
  // Note: This handler is called from the main loop context via esp32_ble's event queue.
299
297
  // We process advertisements immediately instead of buffering them.
300
- ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
298
+ ESP_LOGVV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
301
299
 
302
300
  if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
303
301
  // Process the scan result immediately
@@ -794,7 +792,7 @@ void ESP32BLETracker::try_promote_discovered_clients_() {
794
792
  #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
795
793
  this->update_coex_preference_(true);
796
794
  #endif
797
- client->set_state(ClientState::READY_TO_CONNECT);
795
+ client->connect();
798
796
  break;
799
797
  }
800
798
  }
@@ -159,8 +159,6 @@ enum class ClientState : uint8_t {
159
159
  IDLE,
160
160
  // Device advertisement found.
161
161
  DISCOVERED,
162
- // Device is discovered and the scanner is stopped
163
- READY_TO_CONNECT,
164
162
  // Connection in progress.
165
163
  CONNECTING,
166
164
  // Initial connection established.
@@ -313,7 +311,6 @@ class ESP32BLETracker : public Component,
313
311
  counts.discovered++;
314
312
  break;
315
313
  case ClientState::CONNECTING:
316
- case ClientState::READY_TO_CONNECT:
317
314
  counts.connecting++;
318
315
  break;
319
316
  default:
@@ -21,7 +21,6 @@ from esphome.const import (
21
21
  CONF_TRIGGER_ID,
22
22
  CONF_VSYNC_PIN,
23
23
  )
24
- from esphome.core import CORE
25
24
  from esphome.core.entity_helpers import setup_entity
26
25
  import esphome.final_validate as fv
27
26
 
@@ -344,8 +343,7 @@ async def to_code(config):
344
343
 
345
344
  cg.add_define("USE_CAMERA")
346
345
 
347
- if CORE.using_esp_idf:
348
- add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
346
+ add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
349
347
 
350
348
  for conf in config.get(CONF_ON_STREAM_START, []):
351
349
  trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
@@ -67,8 +67,16 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config
67
67
  }
68
68
 
69
69
  bool ESP32Can::setup_internal() {
70
+ static int next_twai_ctrl_num = 0;
71
+ if (static_cast<unsigned>(next_twai_ctrl_num) >= SOC_TWAI_CONTROLLER_NUM) {
72
+ ESP_LOGW(TAG, "Maximum number of esp32_can components created already");
73
+ this->mark_failed();
74
+ return false;
75
+ }
76
+
70
77
  twai_general_config_t g_config =
71
78
  TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
79
+ g_config.controller_id = next_twai_ctrl_num++;
72
80
  if (this->tx_queue_len_.has_value()) {
73
81
  g_config.tx_queue_len = this->tx_queue_len_.value();
74
82
  }
@@ -86,14 +94,14 @@ bool ESP32Can::setup_internal() {
86
94
  }
87
95
 
88
96
  // Install TWAI driver
89
- if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) {
97
+ if (twai_driver_install_v2(&g_config, &t_config, &f_config, &(this->twai_handle_)) != ESP_OK) {
90
98
  // Failed to install driver
91
99
  this->mark_failed();
92
100
  return false;
93
101
  }
94
102
 
95
103
  // Start TWAI driver
96
- if (twai_start() != ESP_OK) {
104
+ if (twai_start_v2(this->twai_handle_) != ESP_OK) {
97
105
  // Failed to start driver
98
106
  this->mark_failed();
99
107
  return false;
@@ -102,6 +110,11 @@ bool ESP32Can::setup_internal() {
102
110
  }
103
111
 
104
112
  canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
113
+ if (this->twai_handle_ == nullptr) {
114
+ // not setup yet or setup failed
115
+ return canbus::ERROR_FAIL;
116
+ }
117
+
105
118
  if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
106
119
  return canbus::ERROR_FAILTX;
107
120
  }
@@ -124,7 +137,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
124
137
  memcpy(message.data, frame->data, frame->can_data_length_code);
125
138
  }
126
139
 
127
- if (twai_transmit(&message, this->tx_enqueue_timeout_ticks_) == ESP_OK) {
140
+ if (twai_transmit_v2(this->twai_handle_, &message, this->tx_enqueue_timeout_ticks_) == ESP_OK) {
128
141
  return canbus::ERROR_OK;
129
142
  } else {
130
143
  return canbus::ERROR_ALLTXBUSY;
@@ -132,9 +145,14 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
132
145
  }
133
146
 
134
147
  canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) {
148
+ if (this->twai_handle_ == nullptr) {
149
+ // not setup yet or setup failed
150
+ return canbus::ERROR_FAIL;
151
+ }
152
+
135
153
  twai_message_t message;
136
154
 
137
- if (twai_receive(&message, 0) != ESP_OK) {
155
+ if (twai_receive_v2(this->twai_handle_, &message, 0) != ESP_OK) {
138
156
  return canbus::ERROR_NOMSG;
139
157
  }
140
158
 
@@ -5,6 +5,8 @@
5
5
  #include "esphome/components/canbus/canbus.h"
6
6
  #include "esphome/core/component.h"
7
7
 
8
+ #include <driver/twai.h>
9
+
8
10
  namespace esphome {
9
11
  namespace esp32_can {
10
12
 
@@ -29,6 +31,7 @@ class ESP32Can : public canbus::Canbus {
29
31
  TickType_t tx_enqueue_timeout_ticks_{};
30
32
  optional<uint32_t> tx_queue_len_{};
31
33
  optional<uint32_t> rx_queue_len_{};
34
+ twai_handle_t twai_handle_{nullptr};
32
35
  };
33
36
 
34
37
  } // namespace esp32_can
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from pathlib import Path
2
3
 
3
4
  from esphome import pins
4
5
  from esphome.components import esp32
@@ -97,5 +98,5 @@ async def to_code(config):
97
98
  esp32.add_extra_script(
98
99
  "post",
99
100
  "esp32_hosted.py",
100
- os.path.join(os.path.dirname(__file__), "esp32_hosted.py.script"),
101
+ Path(__file__).parent / "esp32_hosted.py.script",
101
102
  )
@@ -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,24 +136,21 @@ 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
131
146
  break;
132
147
  }
133
148
  case improv::STATE_AUTHORIZED: {
134
149
  #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
- }
150
+ if (this->authorizer_ != nullptr && now - this->authorized_start_ > this->authorized_duration_) {
151
+ ESP_LOGD(TAG, "Authorization timeout");
152
+ this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
153
+ return;
141
154
  }
142
155
  #endif
143
156
  if (!this->check_identify_()) {
@@ -226,12 +239,15 @@ bool ESP32ImprovComponent::check_identify_() {
226
239
  return identify;
227
240
  }
228
241
 
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);
242
+ void ESP32ImprovComponent::set_state_(improv::State state, bool update_advertising) {
243
+ // Skip if state hasn't changed
244
+ if (this->state_ == state) {
245
+ return;
234
246
  }
247
+
248
+ #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
249
+ ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_,
250
+ this->state_to_string_(state), state);
235
251
  #endif
236
252
  this->state_ = state;
237
253
  if (this->status_ != nullptr && (this->status_->get_value().empty() || this->status_->get_value()[0] != state)) {
@@ -243,25 +259,13 @@ void ESP32ImprovComponent::set_state_(improv::State state) {
243
259
  // STATE_STOPPED (0x00) is internal only and not part of the Improv spec.
244
260
  // Advertising 0x00 causes undefined behavior in some clients and makes them
245
261
  // 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);
262
+ if (state != improv::STATE_STOPPED && update_advertising) {
263
+ // State change always overrides name advertising and resets the timer
264
+ this->advertising_device_name_ = false;
265
+ // Reset the timer so we wait another 60 seconds before advertising name
266
+ this->last_name_adv_time_ = App.get_loop_component_start_time();
267
+ // Advertise the new state via service data
268
+ this->advertise_service_data_();
265
269
  }
266
270
  #ifdef USE_ESP32_IMPROV_STATE_CALLBACK
267
271
  this->state_callback_.call(this->state_, this->error_state_);
@@ -388,6 +392,60 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() {
388
392
  wifi::global_wifi_component->clear_sta();
389
393
  }
390
394
 
395
+ void ESP32ImprovComponent::advertise_service_data_() {
396
+ uint8_t service_data[IMPROV_SERVICE_DATA_SIZE] = {};
397
+ service_data[0] = IMPROV_PROTOCOL_ID_1; // PR
398
+ service_data[1] = IMPROV_PROTOCOL_ID_2; // IM
399
+ service_data[2] = static_cast<uint8_t>(this->state_);
400
+
401
+ uint8_t capabilities = 0x00;
402
+ #ifdef USE_OUTPUT
403
+ if (this->status_indicator_ != nullptr)
404
+ capabilities |= improv::CAPABILITY_IDENTIFY;
405
+ #endif
406
+
407
+ service_data[3] = capabilities;
408
+ // service_data[4-7] are already 0 (Reserved)
409
+
410
+ // Atomically set service data and disable name in advertising
411
+ esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>(service_data), false);
412
+ }
413
+
414
+ void ESP32ImprovComponent::update_advertising_type_() {
415
+ uint32_t now = App.get_loop_component_start_time();
416
+
417
+ // If we're advertising the device name and it's been more than NAME_ADVERTISING_DURATION, switch back to service data
418
+ if (this->advertising_device_name_) {
419
+ if (now - this->last_name_adv_time_ >= NAME_ADVERTISING_DURATION) {
420
+ ESP_LOGV(TAG, "Switching back to service data advertising");
421
+ this->advertising_device_name_ = false;
422
+ // Restore service data advertising
423
+ this->advertise_service_data_();
424
+ }
425
+ return;
426
+ }
427
+
428
+ // Check if it's time to advertise the device name (every NAME_ADVERTISING_INTERVAL)
429
+ if (now - this->last_name_adv_time_ >= NAME_ADVERTISING_INTERVAL) {
430
+ ESP_LOGV(TAG, "Switching to device name advertising");
431
+ this->advertising_device_name_ = true;
432
+ this->last_name_adv_time_ = now;
433
+
434
+ // Atomically clear service data and enable name in advertising data
435
+ esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>{}, true);
436
+ }
437
+ }
438
+
439
+ improv::State ESP32ImprovComponent::get_initial_state_() const {
440
+ #ifdef USE_BINARY_SENSOR
441
+ // If we have an authorizer, start in awaiting authorization state
442
+ return this->authorizer_ == nullptr ? improv::STATE_AUTHORIZED : improv::STATE_AWAITING_AUTHORIZATION;
443
+ #else
444
+ // No binary_sensor support = no authorizer possible, start as authorized
445
+ return improv::STATE_AUTHORIZED;
446
+ #endif
447
+ }
448
+
391
449
  ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
392
450
 
393
451
  } // namespace esp32_improv
@@ -100,14 +100,19 @@ 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_();
110
114
  bool check_identify_();
115
+ void advertise_service_data_();
111
116
  #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
112
117
  const char *state_to_string_(improv::State state);
113
118
  #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)