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
@@ -1504,6 +1504,10 @@ BOARDS = {
1504
1504
  "name": "BPI-Bit",
1505
1505
  "variant": VARIANT_ESP32,
1506
1506
  },
1507
+ "bpi-centi-s3": {
1508
+ "name": "BPI-Centi-S3",
1509
+ "variant": VARIANT_ESP32S3,
1510
+ },
1507
1511
  "bpi_leaf_s3": {
1508
1512
  "name": "BPI-Leaf-S3",
1509
1513
  "variant": VARIANT_ESP32S3,
@@ -1664,10 +1668,46 @@ BOARDS = {
1664
1668
  "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
1665
1669
  "variant": VARIANT_ESP32S3,
1666
1670
  },
1671
+ "esp32-s3-devkitc-1-n32r8v": {
1672
+ "name": "Espressif ESP32-S3-DevKitC-1-N32R8V (32 MB Flash Octal, 8 MB PSRAM Octal)",
1673
+ "variant": VARIANT_ESP32S3,
1674
+ },
1675
+ "esp32-s3-devkitc1-n16r16": {
1676
+ "name": "Espressif ESP32-S3-DevKitC-1-N16R16V (16 MB Flash Quad, 16 MB PSRAM Octal)",
1677
+ "variant": VARIANT_ESP32S3,
1678
+ },
1679
+ "esp32-s3-devkitc1-n16r2": {
1680
+ "name": "Espressif ESP32-S3-DevKitC-1-N16R2 (16 MB Flash Quad, 2 MB PSRAM Quad)",
1681
+ "variant": VARIANT_ESP32S3,
1682
+ },
1683
+ "esp32-s3-devkitc1-n16r8": {
1684
+ "name": "Espressif ESP32-S3-DevKitC-1-N16R8V (16 MB Flash Quad, 8 MB PSRAM Octal)",
1685
+ "variant": VARIANT_ESP32S3,
1686
+ },
1687
+ "esp32-s3-devkitc1-n4r2": {
1688
+ "name": "Espressif ESP32-S3-DevKitC-1-N4R2 (4 MB Flash Quad, 2 MB PSRAM Quad)",
1689
+ "variant": VARIANT_ESP32S3,
1690
+ },
1691
+ "esp32-s3-devkitc1-n4r8": {
1692
+ "name": "Espressif ESP32-S3-DevKitC-1-N4R8 (4 MB Flash Quad, 8 MB PSRAM Octal)",
1693
+ "variant": VARIANT_ESP32S3,
1694
+ },
1695
+ "esp32-s3-devkitc1-n8r2": {
1696
+ "name": "Espressif ESP32-S3-DevKitC-1-N8R2 (8 MB Flash Quad, 2 MB PSRAM quad)",
1697
+ "variant": VARIANT_ESP32S3,
1698
+ },
1699
+ "esp32-s3-devkitc1-n8r8": {
1700
+ "name": "Espressif ESP32-S3-DevKitC-1-N8R8 (8 MB Flash Quad, 8 MB PSRAM Octal)",
1701
+ "variant": VARIANT_ESP32S3,
1702
+ },
1667
1703
  "esp32-s3-devkitm-1": {
1668
1704
  "name": "Espressif ESP32-S3-DevKitM-1",
1669
1705
  "variant": VARIANT_ESP32S3,
1670
1706
  },
1707
+ "esp32-s3-fh4r2": {
1708
+ "name": "Espressif ESP32-S3-FH4R2 (4 MB QD, 2MB PSRAM)",
1709
+ "variant": VARIANT_ESP32S3,
1710
+ },
1671
1711
  "esp32-solo1": {
1672
1712
  "name": "Espressif Generic ESP32-solo1 4M Flash",
1673
1713
  "variant": VARIANT_ESP32,
@@ -1764,6 +1804,10 @@ BOARDS = {
1764
1804
  "name": "Franzininho WiFi MSC",
1765
1805
  "variant": VARIANT_ESP32S2,
1766
1806
  },
1807
+ "freenove-esp32-s3-n8r8": {
1808
+ "name": "Freenove ESP32-S3 WROOM N8R8 (8MB Flash / 8MB PSRAM)",
1809
+ "variant": VARIANT_ESP32S3,
1810
+ },
1767
1811
  "freenove_esp32_s3_wroom": {
1768
1812
  "name": "Freenove ESP32-S3 WROOM N8R8 (8MB Flash / 8MB PSRAM)",
1769
1813
  "variant": VARIANT_ESP32S3,
@@ -1964,6 +2008,10 @@ BOARDS = {
1964
2008
  "name": "M5Stack AtomS3",
1965
2009
  "variant": VARIANT_ESP32S3,
1966
2010
  },
2011
+ "m5stack-atoms3u": {
2012
+ "name": "M5Stack AtomS3U",
2013
+ "variant": VARIANT_ESP32S3,
2014
+ },
1967
2015
  "m5stack-core-esp32": {
1968
2016
  "name": "M5Stack Core ESP32",
1969
2017
  "variant": VARIANT_ESP32,
@@ -2084,6 +2132,10 @@ BOARDS = {
2084
2132
  "name": "Ai-Thinker NodeMCU-32S2 (ESP-12K)",
2085
2133
  "variant": VARIANT_ESP32S2,
2086
2134
  },
2135
+ "nologo_esp32c3_super_mini": {
2136
+ "name": "Nologo ESP32C3 SuperMini",
2137
+ "variant": VARIANT_ESP32C3,
2138
+ },
2087
2139
  "nscreen-32": {
2088
2140
  "name": "YeaCreate NSCREEN-32",
2089
2141
  "variant": VARIANT_ESP32,
@@ -2192,6 +2244,10 @@ BOARDS = {
2192
2244
  "name": "SparkFun LoRa Gateway 1-Channel",
2193
2245
  "variant": VARIANT_ESP32,
2194
2246
  },
2247
+ "sparkfun_pro_micro_esp32c3": {
2248
+ "name": "SparkFun Pro Micro ESP32-C3",
2249
+ "variant": VARIANT_ESP32C3,
2250
+ },
2195
2251
  "sparkfun_qwiic_pocket_esp32c6": {
2196
2252
  "name": "SparkFun ESP32-C6 Qwiic Pocket",
2197
2253
  "variant": VARIANT_ESP32C6,
@@ -2256,6 +2312,14 @@ BOARDS = {
2256
2312
  "name": "Turta IoT Node",
2257
2313
  "variant": VARIANT_ESP32,
2258
2314
  },
2315
+ "um_bling": {
2316
+ "name": "Unexpected Maker BLING!",
2317
+ "variant": VARIANT_ESP32S3,
2318
+ },
2319
+ "um_edges3_d": {
2320
+ "name": "Unexpected Maker EDGES3[D]",
2321
+ "variant": VARIANT_ESP32S3,
2322
+ },
2259
2323
  "um_feathers2": {
2260
2324
  "name": "Unexpected Maker FeatherS2",
2261
2325
  "variant": VARIANT_ESP32S2,
@@ -2268,10 +2332,18 @@ BOARDS = {
2268
2332
  "name": "Unexpected Maker FeatherS3",
2269
2333
  "variant": VARIANT_ESP32S3,
2270
2334
  },
2335
+ "um_feathers3_neo": {
2336
+ "name": "Unexpected Maker FeatherS3 Neo",
2337
+ "variant": VARIANT_ESP32S3,
2338
+ },
2271
2339
  "um_nanos3": {
2272
2340
  "name": "Unexpected Maker NanoS3",
2273
2341
  "variant": VARIANT_ESP32S3,
2274
2342
  },
2343
+ "um_omgs3": {
2344
+ "name": "Unexpected Maker OMGS3",
2345
+ "variant": VARIANT_ESP32S3,
2346
+ },
2275
2347
  "um_pros3": {
2276
2348
  "name": "Unexpected Maker PROS3",
2277
2349
  "variant": VARIANT_ESP32S3,
@@ -2280,6 +2352,14 @@ BOARDS = {
2280
2352
  "name": "Unexpected Maker RMP",
2281
2353
  "variant": VARIANT_ESP32S2,
2282
2354
  },
2355
+ "um_squixl": {
2356
+ "name": "Unexpected Maker SQUiXL",
2357
+ "variant": VARIANT_ESP32S3,
2358
+ },
2359
+ "um_tinyc6": {
2360
+ "name": "Unexpected Maker TinyC6",
2361
+ "variant": VARIANT_ESP32C6,
2362
+ },
2283
2363
  "um_tinys2": {
2284
2364
  "name": "Unexpected Maker TinyS2",
2285
2365
  "variant": VARIANT_ESP32S2,
@@ -2401,3 +2481,4 @@ BOARDS = {
2401
2481
  "variant": VARIANT_ESP32S3,
2402
2482
  },
2403
2483
  }
2484
+ # DO NOT ADD ANYTHING BELOW THIS LINE
@@ -17,7 +17,14 @@ static const char *const TAG = "esp32.preferences";
17
17
 
18
18
  struct NVSData {
19
19
  std::string key;
20
- std::vector<uint8_t> data;
20
+ std::unique_ptr<uint8_t[]> data;
21
+ size_t len;
22
+
23
+ void set_data(const uint8_t *src, size_t size) {
24
+ data = std::make_unique<uint8_t[]>(size);
25
+ memcpy(data.get(), src, size);
26
+ len = size;
27
+ }
21
28
  };
22
29
 
23
30
  static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -30,26 +37,26 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
30
37
  // try find in pending saves and update that
31
38
  for (auto &obj : s_pending_save) {
32
39
  if (obj.key == key) {
33
- obj.data.assign(data, data + len);
40
+ obj.set_data(data, len);
34
41
  return true;
35
42
  }
36
43
  }
37
44
  NVSData save{};
38
45
  save.key = key;
39
- save.data.assign(data, data + len);
40
- s_pending_save.emplace_back(save);
41
- ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %d", key.c_str(), len);
46
+ save.set_data(data, len);
47
+ s_pending_save.emplace_back(std::move(save));
48
+ ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len);
42
49
  return true;
43
50
  }
44
51
  bool load(uint8_t *data, size_t len) override {
45
52
  // try find in pending saves and load from that
46
53
  for (auto &obj : s_pending_save) {
47
54
  if (obj.key == key) {
48
- if (obj.data.size() != len) {
55
+ if (obj.len != len) {
49
56
  // size mismatch
50
57
  return false;
51
58
  }
52
- memcpy(data, obj.data.data(), len);
59
+ memcpy(data, obj.data.get(), len);
53
60
  return true;
54
61
  }
55
62
  }
@@ -61,7 +68,7 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
61
68
  return false;
62
69
  }
63
70
  if (actual_len != len) {
64
- ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len);
71
+ ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
65
72
  return false;
66
73
  }
67
74
  err = nvs_get_blob(nvs_handle, key.c_str(), data, &len);
@@ -69,7 +76,7 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
69
76
  ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err));
70
77
  return false;
71
78
  } else {
72
- ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %d", key.c_str(), len);
79
+ ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len);
73
80
  }
74
81
  return true;
75
82
  }
@@ -112,7 +119,7 @@ class ESP32Preferences : public ESPPreferences {
112
119
  if (s_pending_save.empty())
113
120
  return true;
114
121
 
115
- ESP_LOGV(TAG, "Saving %d items...", s_pending_save.size());
122
+ ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size());
116
123
  // goal try write all pending saves even if one fails
117
124
  int cached = 0, written = 0, failed = 0;
118
125
  esp_err_t last_err = ESP_OK;
@@ -123,11 +130,10 @@ class ESP32Preferences : public ESPPreferences {
123
130
  const auto &save = s_pending_save[i];
124
131
  ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str());
125
132
  if (is_changed(nvs_handle, save)) {
126
- esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size());
127
- ESP_LOGV(TAG, "sync: key: %s, len: %d", save.key.c_str(), save.data.size());
133
+ esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len);
134
+ ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len);
128
135
  if (err != 0) {
129
- ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(),
130
- esp_err_to_name(err));
136
+ ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err));
131
137
  failed++;
132
138
  last_err = err;
133
139
  last_key = save.key;
@@ -135,7 +141,7 @@ class ESP32Preferences : public ESPPreferences {
135
141
  }
136
142
  written++;
137
143
  } else {
138
- ESP_LOGV(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size());
144
+ ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len);
139
145
  cached++;
140
146
  }
141
147
  s_pending_save.erase(s_pending_save.begin() + i);
@@ -164,7 +170,7 @@ class ESP32Preferences : public ESPPreferences {
164
170
  return true;
165
171
  }
166
172
  // Check size first before allocating memory
167
- if (actual_len != to_save.data.size()) {
173
+ if (actual_len != to_save.len) {
168
174
  return true;
169
175
  }
170
176
  auto stored_data = std::make_unique<uint8_t[]>(actual_len);
@@ -173,7 +179,7 @@ class ESP32Preferences : public ESPPreferences {
173
179
  ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
174
180
  return true;
175
181
  }
176
- return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
182
+ return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;
177
183
  }
178
184
 
179
185
  bool reset() override {
@@ -1,5 +1,8 @@
1
+ from collections.abc import Callable, MutableMapping
1
2
  from enum import Enum
3
+ import logging
2
4
  import re
5
+ from typing import Any
3
6
 
4
7
  from esphome import automation
5
8
  import esphome.codegen as cg
@@ -9,6 +12,7 @@ from esphome.const import (
9
12
  CONF_ENABLE_ON_BOOT,
10
13
  CONF_ESPHOME,
11
14
  CONF_ID,
15
+ CONF_MAX_CONNECTIONS,
12
16
  CONF_NAME,
13
17
  CONF_NAME_ADD_MAC_SUFFIX,
14
18
  )
@@ -19,6 +23,8 @@ DEPENDENCIES = ["esp32"]
19
23
  CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
20
24
  DOMAIN = "esp32_ble"
21
25
 
26
+ _LOGGER = logging.getLogger(__name__)
27
+
22
28
 
23
29
  class BTLoggers(Enum):
24
30
  """Bluetooth logger categories available in ESP-IDF.
@@ -127,6 +133,28 @@ CONF_DISABLE_BT_LOGS = "disable_bt_logs"
127
133
  CONF_CONNECTION_TIMEOUT = "connection_timeout"
128
134
  CONF_MAX_NOTIFICATIONS = "max_notifications"
129
135
 
136
+ # BLE connection limits
137
+ # ESP-IDF CONFIG_BT_ACL_CONNECTIONS has range 1-9, default 4
138
+ # Total instances: 10 (ADV + SCAN + connections)
139
+ # - ADV only: up to 9 connections
140
+ # - SCAN only: up to 9 connections
141
+ # - ADV + SCAN: up to 8 connections
142
+ DEFAULT_MAX_CONNECTIONS = 3
143
+ IDF_MAX_CONNECTIONS = 9
144
+
145
+ # Connection slot tracking keys
146
+ KEY_ESP32_BLE = "esp32_ble"
147
+ KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
148
+
149
+ # Export for use by other components (bluetooth_proxy, etc.)
150
+ __all__ = [
151
+ "DEFAULT_MAX_CONNECTIONS",
152
+ "IDF_MAX_CONNECTIONS",
153
+ "KEY_ESP32_BLE",
154
+ "KEY_USED_CONNECTION_SLOTS",
155
+ "consume_connection_slots",
156
+ ]
157
+
130
158
  NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
131
159
 
132
160
  esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
@@ -174,19 +202,18 @@ CONFIG_SCHEMA = cv.Schema(
174
202
  cv.Optional(
175
203
  CONF_ADVERTISING_CYCLE_TIME, default="10s"
176
204
  ): cv.positive_time_period_milliseconds,
177
- cv.SplitDefault(CONF_DISABLE_BT_LOGS, esp32_idf=True): cv.All(
178
- cv.only_with_esp_idf, cv.boolean
179
- ),
180
- cv.SplitDefault(CONF_CONNECTION_TIMEOUT, esp32_idf="20s"): cv.All(
181
- cv.only_with_esp_idf,
205
+ cv.Optional(CONF_DISABLE_BT_LOGS, default=True): cv.boolean,
206
+ cv.Optional(CONF_CONNECTION_TIMEOUT, default="20s"): cv.All(
182
207
  cv.positive_time_period_seconds,
183
208
  cv.Range(min=TimePeriod(seconds=10), max=TimePeriod(seconds=180)),
184
209
  ),
185
- cv.SplitDefault(CONF_MAX_NOTIFICATIONS, esp32_idf=12): cv.All(
186
- cv.only_with_esp_idf,
210
+ cv.Optional(CONF_MAX_NOTIFICATIONS, default=12): cv.All(
187
211
  cv.positive_int,
188
212
  cv.Range(min=1, max=64),
189
213
  ),
214
+ cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All(
215
+ cv.positive_int, cv.Range(min=1, max=IDF_MAX_CONNECTIONS)
216
+ ),
190
217
  }
191
218
  ).extend(cv.COMPONENT_SCHEMA)
192
219
 
@@ -234,6 +261,56 @@ def validate_variant(_):
234
261
  raise cv.Invalid(f"{variant} does not support Bluetooth")
235
262
 
236
263
 
264
+ def consume_connection_slots(
265
+ value: int, consumer: str
266
+ ) -> Callable[[MutableMapping], MutableMapping]:
267
+ """Reserve BLE connection slots for a component.
268
+
269
+ Args:
270
+ value: Number of connection slots to reserve
271
+ consumer: Name of the component consuming the slots
272
+
273
+ Returns:
274
+ A validator function that records the slot usage
275
+ """
276
+
277
+ def _consume_connection_slots(config: MutableMapping) -> MutableMapping:
278
+ data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE, {})
279
+ slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, [])
280
+ slots.extend([consumer] * value)
281
+ return config
282
+
283
+ return _consume_connection_slots
284
+
285
+
286
+ def validate_connection_slots(max_connections: int) -> None:
287
+ """Validate that BLE connection slots don't exceed the configured maximum."""
288
+ ble_data = CORE.data.get(KEY_ESP32_BLE, {})
289
+ used_slots = ble_data.get(KEY_USED_CONNECTION_SLOTS, [])
290
+ num_used = len(used_slots)
291
+
292
+ if num_used <= max_connections:
293
+ return
294
+
295
+ slot_users = ", ".join(used_slots)
296
+
297
+ if num_used > IDF_MAX_CONNECTIONS:
298
+ raise cv.Invalid(
299
+ f"BLE components require {num_used} connection slots but maximum is {IDF_MAX_CONNECTIONS}. "
300
+ f"Reduce the number of BLE clients. Components: {slot_users}"
301
+ )
302
+
303
+ _LOGGER.warning(
304
+ "BLE components require %d connection slot(s) but only %d configured. "
305
+ "Please set 'max_connections: %d' in the 'esp32_ble' component. "
306
+ "Components: %s",
307
+ num_used,
308
+ max_connections,
309
+ num_used,
310
+ slot_users,
311
+ )
312
+
313
+
237
314
  def final_validation(config):
238
315
  validate_variant(config)
239
316
  if (name := config.get(CONF_NAME)) is not None:
@@ -246,6 +323,43 @@ def final_validation(config):
246
323
  f"Name '{name}' is too long, maximum length is {max_length} characters"
247
324
  )
248
325
 
326
+ # Set GATT Client/Server sdkconfig options based on which components are loaded
327
+ full_config = fv.full_config.get()
328
+
329
+ # Validate connection slots usage
330
+ max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
331
+ validate_connection_slots(max_connections)
332
+
333
+ # Check if BLE Server is needed
334
+ has_ble_server = "esp32_ble_server" in full_config
335
+ add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server)
336
+
337
+ # Check if BLE Client is needed (via esp32_ble_tracker or esp32_ble_client)
338
+ has_ble_client = (
339
+ "esp32_ble_tracker" in full_config or "esp32_ble_client" in full_config
340
+ )
341
+ add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client)
342
+
343
+ # Handle max_connections: check for deprecated location in esp32_ble_tracker
344
+ max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
345
+
346
+ # Use value from tracker if esp32_ble doesn't have it explicitly set (backward compat)
347
+ if "esp32_ble_tracker" in full_config:
348
+ tracker_config = full_config["esp32_ble_tracker"]
349
+ if "max_connections" in tracker_config and CONF_MAX_CONNECTIONS not in config:
350
+ max_connections = tracker_config["max_connections"]
351
+
352
+ # Set CONFIG_BT_ACL_CONNECTIONS to the maximum connections needed + 1 for ADV/SCAN
353
+ # This is the Bluedroid host stack total instance limit (range 1-9, default 4)
354
+ # Total instances = ADV/SCAN (1) + connection slots (max_connections)
355
+ # Shared between client (tracker/ble_client) and server
356
+ add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", max_connections + 1)
357
+
358
+ # Set controller-specific max connections for ESP32 (classic)
359
+ # CONFIG_BTDM_CTRL_BLE_MAX_CONN is ESP32-specific controller limit (just connections, not ADV/SCAN)
360
+ # For newer chips (C3/S3/etc), different configs are used automatically
361
+ add_idf_sdkconfig_option("CONFIG_BTDM_CTRL_BLE_MAX_CONN", max_connections)
362
+
249
363
  return config
250
364
 
251
365
 
@@ -261,43 +375,44 @@ async def to_code(config):
261
375
  cg.add(var.set_name(name))
262
376
  await cg.register_component(var, config)
263
377
 
264
- if CORE.using_esp_idf:
265
- add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
266
- add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
267
-
268
- # Register the core BLE loggers that are always needed
269
- register_bt_logger(BTLoggers.GAP, BTLoggers.BTM, BTLoggers.HCI)
270
-
271
- # Apply logger settings if log disabling is enabled
272
- if config.get(CONF_DISABLE_BT_LOGS, False):
273
- # Disable all Bluetooth loggers that are not required
274
- for logger in BTLoggers:
275
- if logger not in _required_loggers:
276
- add_idf_sdkconfig_option(f"{logger.value}_NONE", True)
277
-
278
- # Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector
279
- # Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to
280
- # cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds,
281
- # the connection slot remains occupied for the remaining time, preventing new connection
282
- # attempts and wasting valuable connection slots.
283
- if CONF_CONNECTION_TIMEOUT in config:
284
- timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds)
285
- add_idf_sdkconfig_option(
286
- "CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
287
- )
288
- # Increase GATT client connection retry count for problematic devices
289
- # Default in ESP-IDF is 3, we increase to 10 for better reliability with
290
- # low-power/timing-sensitive devices
291
- add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
292
-
293
- # Set the maximum number of notification registrations
294
- # This controls how many BLE characteristics can have notifications enabled
295
- # across all connections for a single GATT client interface
296
- # https://github.com/esphome/issues/issues/6808
297
- if CONF_MAX_NOTIFICATIONS in config:
298
- add_idf_sdkconfig_option(
299
- "CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS]
300
- )
378
+ # Define max connections for use in C++ code (e.g., ble_server.h)
379
+ max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
380
+ cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
381
+
382
+ add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
383
+ add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
384
+
385
+ # Register the core BLE loggers that are always needed
386
+ register_bt_logger(BTLoggers.GAP, BTLoggers.BTM, BTLoggers.HCI)
387
+
388
+ # Apply logger settings if log disabling is enabled
389
+ if config.get(CONF_DISABLE_BT_LOGS, False):
390
+ # Disable all Bluetooth loggers that are not required
391
+ for logger in BTLoggers:
392
+ if logger not in _required_loggers:
393
+ add_idf_sdkconfig_option(f"{logger.value}_NONE", True)
394
+
395
+ # Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector
396
+ # Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to
397
+ # cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds,
398
+ # the connection slot remains occupied for the remaining time, preventing new connection
399
+ # attempts and wasting valuable connection slots.
400
+ if CONF_CONNECTION_TIMEOUT in config:
401
+ timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds)
402
+ add_idf_sdkconfig_option("CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds)
403
+ # Increase GATT client connection retry count for problematic devices
404
+ # Default in ESP-IDF is 3, we increase to 10 for better reliability with
405
+ # low-power/timing-sensitive devices
406
+ add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
407
+
408
+ # Set the maximum number of notification registrations
409
+ # This controls how many BLE characteristics can have notifications enabled
410
+ # across all connections for a single GATT client interface
411
+ # https://github.com/esphome/issues/issues/6808
412
+ if CONF_MAX_NOTIFICATIONS in config:
413
+ add_idf_sdkconfig_option(
414
+ "CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS]
415
+ )
301
416
 
302
417
  cg.add_define("USE_ESP32_BLE")
303
418
 
@@ -73,6 +73,28 @@ void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &dat
73
73
  this->advertising_start();
74
74
  }
75
75
 
76
+ void ESP32BLE::advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name) {
77
+ // This method atomically updates both service data and device name inclusion in BLE advertising.
78
+ // When include_name is true, the device name is included in the advertising packet making it
79
+ // visible to passive BLE scanners. When false, the name is only visible in scan response
80
+ // (requires active scanning). This atomic operation ensures we only restart advertising once
81
+ // when changing both properties, avoiding the brief gap that would occur with separate calls.
82
+
83
+ this->advertising_init_();
84
+
85
+ if (include_name) {
86
+ // When including name, clear service data first to avoid packet overflow
87
+ this->advertising_->set_service_data(std::span<const uint8_t>{});
88
+ this->advertising_->set_include_name(true);
89
+ } else {
90
+ // When including service data, clear name first to avoid packet overflow
91
+ this->advertising_->set_include_name(false);
92
+ this->advertising_->set_service_data(data);
93
+ }
94
+
95
+ this->advertising_start();
96
+ }
97
+
76
98
  void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
77
99
  this->advertising_init_();
78
100
  this->advertising_->register_raw_advertisement_callback(std::move(callback));
@@ -167,6 +189,7 @@ bool ESP32BLE::ble_setup_() {
167
189
  }
168
190
  }
169
191
 
192
+ #ifdef USE_ESP32_BLE_SERVER
170
193
  if (!this->gatts_event_handlers_.empty()) {
171
194
  err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
172
195
  if (err != ESP_OK) {
@@ -174,7 +197,9 @@ bool ESP32BLE::ble_setup_() {
174
197
  return false;
175
198
  }
176
199
  }
200
+ #endif
177
201
 
202
+ #ifdef USE_ESP32_BLE_CLIENT
178
203
  if (!this->gattc_event_handlers_.empty()) {
179
204
  err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
180
205
  if (err != ESP_OK) {
@@ -182,20 +207,23 @@ bool ESP32BLE::ble_setup_() {
182
207
  return false;
183
208
  }
184
209
  }
210
+ #endif
185
211
 
186
212
  std::string name;
187
213
  if (this->name_.has_value()) {
188
214
  name = this->name_.value();
189
215
  if (App.is_name_add_mac_suffix_enabled()) {
190
- name += "-" + get_mac_address().substr(6);
216
+ name += "-";
217
+ name += get_mac_address().substr(6);
191
218
  }
192
219
  } else {
193
220
  name = App.get_name();
194
221
  if (name.length() > 20) {
195
222
  if (App.is_name_add_mac_suffix_enabled()) {
196
- name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
223
+ // Keep first 13 chars and last 7 chars (MAC suffix), remove middle
224
+ name.erase(13, name.length() - 20);
197
225
  } else {
198
- name = name.substr(0, 20);
226
+ name.resize(20);
199
227
  }
200
228
  }
201
229
  }
@@ -303,6 +331,7 @@ void ESP32BLE::loop() {
303
331
  BLEEvent *ble_event = this->ble_events_.pop();
304
332
  while (ble_event != nullptr) {
305
333
  switch (ble_event->type_) {
334
+ #ifdef USE_ESP32_BLE_SERVER
306
335
  case BLEEvent::GATTS: {
307
336
  esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
308
337
  esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
@@ -313,6 +342,8 @@ void ESP32BLE::loop() {
313
342
  }
314
343
  break;
315
344
  }
345
+ #endif
346
+ #ifdef USE_ESP32_BLE_CLIENT
316
347
  case BLEEvent::GATTC: {
317
348
  esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
318
349
  esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
@@ -323,6 +354,7 @@ void ESP32BLE::loop() {
323
354
  }
324
355
  break;
325
356
  }
357
+ #endif
326
358
  case BLEEvent::GAP: {
327
359
  esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
328
360
  switch (gap_event) {
@@ -416,13 +448,17 @@ void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_pa
416
448
  event->load_gap_event(e, p);
417
449
  }
418
450
 
451
+ #ifdef USE_ESP32_BLE_CLIENT
419
452
  void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
420
453
  event->load_gattc_event(e, i, p);
421
454
  }
455
+ #endif
422
456
 
457
+ #ifdef USE_ESP32_BLE_SERVER
423
458
  void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
424
459
  event->load_gatts_event(e, i, p);
425
460
  }
461
+ #endif
426
462
 
427
463
  template<typename... Args> void enqueue_ble_event(Args... args) {
428
464
  // Allocate an event from the pool
@@ -443,8 +479,12 @@ template<typename... Args> void enqueue_ble_event(Args... args) {
443
479
 
444
480
  // Explicit template instantiations for the friend function
445
481
  template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
482
+ #ifdef USE_ESP32_BLE_SERVER
446
483
  template void enqueue_ble_event(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *);
484
+ #endif
485
+ #ifdef USE_ESP32_BLE_CLIENT
447
486
  template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *);
487
+ #endif
448
488
 
449
489
  void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
450
490
  switch (event) {
@@ -484,15 +524,19 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa
484
524
  ESP_LOGW(TAG, "Ignoring unexpected GAP event type: %d", event);
485
525
  }
486
526
 
527
+ #ifdef USE_ESP32_BLE_SERVER
487
528
  void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
488
529
  esp_ble_gatts_cb_param_t *param) {
489
530
  enqueue_ble_event(event, gatts_if, param);
490
531
  }
532
+ #endif
491
533
 
534
+ #ifdef USE_ESP32_BLE_CLIENT
492
535
  void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
493
536
  esp_ble_gattc_cb_param_t *param) {
494
537
  enqueue_ble_event(event, gattc_if, param);
495
538
  }
539
+ #endif
496
540
 
497
541
  float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
498
542