esphome 2025.4.1__py3-none-any.whl → 2025.5.0__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 (457) hide show
  1. esphome/__main__.py +16 -14
  2. esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
  3. esphome/components/adc/__init__.py +51 -34
  4. esphome/components/airthings_wave_base/__init__.py +1 -1
  5. esphome/components/alarm_control_panel/__init__.py +37 -2
  6. esphome/components/am43/cover/__init__.py +4 -5
  7. esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
  8. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
  9. esphome/components/analog_threshold/binary_sensor.py +10 -8
  10. esphome/components/anova/climate.py +4 -5
  11. esphome/components/api/__init__.py +25 -8
  12. esphome/components/api/api_connection.cpp +416 -662
  13. esphome/components/api/api_connection.h +256 -57
  14. esphome/components/api/api_frame_helper.cpp +232 -177
  15. esphome/components/api/api_frame_helper.h +61 -8
  16. esphome/components/api/api_noise_context.h +13 -4
  17. esphome/components/api/api_pb2.cpp +1422 -1
  18. esphome/components/api/api_pb2.h +255 -1
  19. esphome/components/api/api_pb2_service.cpp +162 -49
  20. esphome/components/api/api_pb2_service.h +90 -51
  21. esphome/components/api/api_pb2_size.h +361 -0
  22. esphome/components/api/api_server.cpp +110 -34
  23. esphome/components/api/api_server.h +8 -0
  24. esphome/components/api/proto.h +86 -17
  25. esphome/components/as3935_i2c/as3935_i2c.h +0 -3
  26. esphome/components/as7341/as7341.h +1 -1
  27. esphome/components/at581x/at581x.h +4 -4
  28. esphome/components/atm90e32/__init__.py +1 -0
  29. esphome/components/atm90e32/atm90e32.cpp +576 -199
  30. esphome/components/atm90e32/atm90e32.h +128 -31
  31. esphome/components/atm90e32/atm90e32_reg.h +4 -2
  32. esphome/components/atm90e32/button/__init__.py +62 -10
  33. esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
  34. esphome/components/atm90e32/button/atm90e32_button.h +36 -4
  35. esphome/components/atm90e32/number/__init__.py +130 -0
  36. esphome/components/atm90e32/number/atm90e32_number.h +16 -0
  37. esphome/components/atm90e32/sensor.py +21 -4
  38. esphome/components/atm90e32/text_sensor/__init__.py +48 -0
  39. esphome/components/audio/__init__.py +96 -49
  40. esphome/components/audio/audio.h +48 -0
  41. esphome/components/audio/audio_decoder.cpp +1 -1
  42. esphome/components/audio/audio_resampler.cpp +2 -0
  43. esphome/components/audio/audio_resampler.h +1 -0
  44. esphome/components/ballu/climate.py +2 -9
  45. esphome/components/bang_bang/climate.py +5 -6
  46. esphome/components/bedjet/bedjet_hub.cpp +1 -0
  47. esphome/components/bedjet/climate/__init__.py +3 -8
  48. esphome/components/bedjet/fan/__init__.py +2 -11
  49. esphome/components/binary/fan/__init__.py +13 -16
  50. esphome/components/binary_sensor/__init__.py +13 -10
  51. esphome/components/bl0906/constants.h +16 -16
  52. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  53. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  54. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
  55. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  56. esphome/components/button/__init__.py +11 -8
  57. esphome/components/canbus/canbus.cpp +3 -0
  58. esphome/components/canbus/canbus.h +16 -0
  59. esphome/components/ccs811/sensor.py +9 -6
  60. esphome/components/climate/__init__.py +35 -2
  61. esphome/components/climate/climate_mode.h +1 -1
  62. esphome/components/climate/climate_traits.h +63 -57
  63. esphome/components/climate_ir/__init__.py +57 -17
  64. esphome/components/climate_ir_lg/climate.py +2 -5
  65. esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
  66. esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
  67. esphome/components/color/__init__.py +2 -0
  68. esphome/components/const/__init__.py +5 -0
  69. esphome/components/coolix/climate.py +2 -9
  70. esphome/components/copy/cover/__init__.py +10 -9
  71. esphome/components/copy/fan/__init__.py +11 -9
  72. esphome/components/copy/lock/__init__.py +11 -9
  73. esphome/components/copy/text/__init__.py +9 -6
  74. esphome/components/cover/__init__.py +37 -2
  75. esphome/components/cse7766/cse7766.cpp +2 -1
  76. esphome/components/cst226/binary_sensor/__init__.py +28 -0
  77. esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
  78. esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
  79. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
  80. esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
  81. esphome/components/current_based/cover.py +37 -36
  82. esphome/components/current_based/current_based_cover.cpp +2 -1
  83. esphome/components/daikin/climate.py +2 -9
  84. esphome/components/daikin/daikin.cpp +15 -9
  85. esphome/components/daikin/daikin.h +5 -5
  86. esphome/components/daikin_arc/climate.py +2 -7
  87. esphome/components/daikin_brc/climate.py +3 -5
  88. esphome/components/dallas_temp/dallas_temp.cpp +17 -24
  89. esphome/components/dallas_temp/dallas_temp.h +0 -1
  90. esphome/components/daly_bms/daly_bms.cpp +2 -1
  91. esphome/components/debug/debug_component.cpp +6 -1
  92. esphome/components/debug/debug_component.h +8 -0
  93. esphome/components/debug/debug_esp32.cpp +109 -254
  94. esphome/components/debug/sensor.py +14 -0
  95. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  96. esphome/components/delonghi/climate.py +2 -9
  97. esphome/components/demo/__init__.py +18 -20
  98. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  99. esphome/components/display/rect.cpp +4 -9
  100. esphome/components/display/rect.h +1 -1
  101. esphome/components/dps310/sensor.py +6 -6
  102. esphome/components/ee895/sensor.py +9 -9
  103. esphome/components/emmeti/climate.py +2 -9
  104. esphome/components/endstop/cover.py +17 -16
  105. esphome/components/endstop/endstop_cover.cpp +2 -1
  106. esphome/components/ens160_base/__init__.py +12 -9
  107. esphome/components/esp32/__init__.py +60 -3
  108. esphome/components/esp32/core.cpp +11 -5
  109. esphome/components/esp32/gpio.cpp +86 -24
  110. esphome/components/esp32/gpio.py +15 -16
  111. esphome/components/esp32/gpio_esp32.py +1 -2
  112. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  113. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  114. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  115. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  116. esphome/components/esp32_ble/ble.cpp +1 -8
  117. esphome/components/esp32_ble/ble.h +5 -3
  118. esphome/components/esp32_ble/ble_advertising.cpp +2 -1
  119. esphome/components/esp32_ble/ble_advertising.h +1 -0
  120. esphome/components/esp32_ble_server/__init__.py +3 -0
  121. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  122. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  123. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  124. esphome/components/esp32_camera/__init__.py +1 -1
  125. esphome/components/esp32_camera/esp32_camera.cpp +2 -10
  126. esphome/components/esp32_camera/esp32_camera.h +1 -1
  127. esphome/components/esp32_can/esp32_can.cpp +1 -1
  128. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  129. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  130. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  131. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  132. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  133. esphome/components/esp8266/gpio.cpp +69 -8
  134. esphome/components/ethernet/ethernet_component.cpp +1 -1
  135. esphome/components/event/__init__.py +13 -10
  136. esphome/components/factory_reset/switch/__init__.py +7 -21
  137. esphome/components/fan/__init__.py +52 -5
  138. esphome/components/fastled_base/__init__.py +1 -4
  139. esphome/components/fastled_base/fastled_light.cpp +1 -1
  140. esphome/components/feedback/cover.py +38 -33
  141. esphome/components/feedback/feedback_cover.cpp +2 -1
  142. esphome/components/fujitsu_general/climate.py +2 -9
  143. esphome/components/gcja5/gcja5.cpp +2 -1
  144. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  145. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  146. esphome/components/gpio_expander/cached_gpio.h +22 -7
  147. esphome/components/gps/__init__.py +47 -17
  148. esphome/components/gps/gps.cpp +42 -23
  149. esphome/components/gps/gps.h +17 -13
  150. esphome/components/graph/__init__.py +1 -2
  151. esphome/components/gree/climate.py +4 -6
  152. esphome/components/gree/gree.cpp +16 -2
  153. esphome/components/gree/gree.h +2 -2
  154. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  155. esphome/components/haier/climate.py +37 -34
  156. esphome/components/hbridge/fan/__init__.py +19 -17
  157. esphome/components/he60r/cover.py +4 -5
  158. esphome/components/heatpumpir/climate.py +3 -6
  159. esphome/components/hitachi_ac344/climate.py +2 -9
  160. esphome/components/hitachi_ac424/climate.py +2 -9
  161. esphome/components/hlw8012/hlw8012.cpp +1 -1
  162. esphome/components/hm3301/hm3301.h +1 -1
  163. esphome/components/hte501/sensor.py +6 -6
  164. esphome/components/http_request/__init__.py +39 -6
  165. esphome/components/http_request/http_request.cpp +20 -0
  166. esphome/components/http_request/http_request.h +57 -15
  167. esphome/components/http_request/http_request_arduino.cpp +22 -6
  168. esphome/components/http_request/http_request_arduino.h +4 -3
  169. esphome/components/http_request/http_request_host.cpp +141 -0
  170. esphome/components/http_request/http_request_host.h +37 -0
  171. esphome/components/http_request/http_request_idf.cpp +35 -3
  172. esphome/components/http_request/http_request_idf.h +10 -3
  173. esphome/components/http_request/httplib.h +9691 -0
  174. esphome/components/http_request/update/__init__.py +11 -8
  175. esphome/components/hyt271/sensor.py +6 -6
  176. esphome/components/i2c/i2c.h +4 -0
  177. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  178. esphome/components/i2s_audio/__init__.py +131 -22
  179. esphome/components/i2s_audio/i2s_audio.h +44 -4
  180. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  181. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  182. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  183. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  184. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  185. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  186. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  187. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  188. esphome/components/image/__init__.py +37 -17
  189. esphome/components/image/image.cpp +25 -8
  190. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  191. esphome/components/key_collector/__init__.py +35 -0
  192. esphome/components/key_collector/key_collector.cpp +8 -0
  193. esphome/components/key_collector/key_collector.h +10 -0
  194. esphome/components/kuntze/kuntze.cpp +2 -1
  195. esphome/components/ld2410/ld2410.h +1 -1
  196. esphome/components/ld2450/ld2450.h +1 -1
  197. esphome/components/light/__init__.py +57 -0
  198. esphome/components/lock/__init__.py +51 -4
  199. esphome/components/lock/automation.h +2 -13
  200. esphome/components/logger/__init__.py +22 -0
  201. esphome/components/logger/logger.cpp +154 -103
  202. esphome/components/logger/logger.h +211 -36
  203. esphome/components/logger/task_log_buffer.cpp +138 -0
  204. esphome/components/logger/task_log_buffer.h +69 -0
  205. esphome/components/lvgl/__init__.py +13 -5
  206. esphome/components/lvgl/automation.py +50 -1
  207. esphome/components/lvgl/defines.py +0 -1
  208. esphome/components/lvgl/lv_validation.py +10 -1
  209. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  210. esphome/components/lvgl/schemas.py +14 -14
  211. esphome/components/lvgl/text/__init__.py +1 -2
  212. esphome/components/lvgl/widgets/arc.py +7 -6
  213. esphome/components/lvgl/widgets/buttonmatrix.py +3 -3
  214. esphome/components/lvgl/widgets/checkbox.py +2 -2
  215. esphome/components/lvgl/widgets/dropdown.py +2 -1
  216. esphome/components/lvgl/widgets/img.py +15 -12
  217. esphome/components/mapping/__init__.py +134 -0
  218. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  219. esphome/components/max7219digit/max7219digit.cpp +28 -27
  220. esphome/components/mdns/__init__.py +11 -5
  221. esphome/components/mdns/mdns_component.cpp +11 -5
  222. esphome/components/mdns/mdns_component.h +3 -2
  223. esphome/components/mdns/mdns_esp32.cpp +4 -3
  224. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  225. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  226. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  227. esphome/components/media_player/__init__.py +40 -6
  228. esphome/components/mhz19/sensor.py +11 -7
  229. esphome/components/micro_wake_word/__init__.py +99 -31
  230. esphome/components/micro_wake_word/automation.h +54 -0
  231. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  232. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  233. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  234. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  235. esphome/components/micro_wake_word/streaming_model.h +85 -13
  236. esphome/components/microphone/__init__.py +139 -9
  237. esphome/components/microphone/automation.h +14 -2
  238. esphome/components/microphone/microphone.cpp +21 -0
  239. esphome/components/microphone/microphone.h +14 -5
  240. esphome/components/microphone/microphone_source.cpp +95 -0
  241. esphome/components/microphone/microphone_source.h +80 -0
  242. esphome/components/mics_4514/sensor.py +25 -14
  243. esphome/components/midea/climate.py +3 -4
  244. esphome/components/midea_ir/climate.py +3 -5
  245. esphome/components/mipi_spi/__init__.py +15 -0
  246. esphome/components/mipi_spi/display.py +474 -0
  247. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  248. esphome/components/mipi_spi/mipi_spi.h +171 -0
  249. esphome/components/mipi_spi/models/__init__.py +65 -0
  250. esphome/components/mipi_spi/models/amoled.py +72 -0
  251. esphome/components/mipi_spi/models/commands.py +82 -0
  252. esphome/components/mipi_spi/models/cyd.py +10 -0
  253. esphome/components/mipi_spi/models/ili.py +749 -0
  254. esphome/components/mipi_spi/models/jc.py +260 -0
  255. esphome/components/mipi_spi/models/lanbon.py +15 -0
  256. esphome/components/mipi_spi/models/lilygo.py +60 -0
  257. esphome/components/mipi_spi/models/waveshare.py +139 -0
  258. esphome/components/mitsubishi/climate.py +2 -5
  259. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  260. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  261. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  262. esphome/components/mlx90393/sensor.py +5 -0
  263. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  264. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  265. esphome/components/modbus/modbus.cpp +2 -1
  266. esphome/components/mqtt/__init__.py +1 -1
  267. esphome/components/mqtt/mqtt_client.cpp +6 -2
  268. esphome/components/mqtt/mqtt_const.h +4 -0
  269. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  270. esphome/components/mqtt/mqtt_fan.h +2 -0
  271. esphome/components/ms5611/sensor.py +6 -6
  272. esphome/components/ms8607/sensor.py +3 -3
  273. esphome/components/network/__init__.py +1 -1
  274. esphome/components/nextion/base_component.py +17 -16
  275. esphome/components/nextion/display.py +11 -2
  276. esphome/components/nextion/nextion.cpp +39 -1
  277. esphome/components/nextion/nextion.h +50 -0
  278. esphome/components/noblex/climate.py +2 -9
  279. esphome/components/number/__init__.py +12 -9
  280. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  281. esphome/components/one_wire/one_wire_bus.h +14 -8
  282. esphome/components/online_image/bmp_image.cpp +48 -11
  283. esphome/components/online_image/bmp_image.h +2 -0
  284. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  285. esphome/components/opentherm/number/__init__.py +11 -20
  286. esphome/components/opentherm/sensor/__init__.py +3 -3
  287. esphome/components/opentherm/switch/__init__.py +3 -5
  288. esphome/components/output/lock/__init__.py +11 -9
  289. esphome/components/packages/__init__.py +33 -31
  290. esphome/components/packet_transport/__init__.py +201 -0
  291. esphome/components/packet_transport/binary_sensor.py +19 -0
  292. esphome/components/packet_transport/packet_transport.cpp +534 -0
  293. esphome/components/packet_transport/packet_transport.h +154 -0
  294. esphome/components/packet_transport/sensor.py +19 -0
  295. esphome/components/pca9685/pca9685_output.cpp +2 -1
  296. esphome/components/pid/climate.py +2 -4
  297. esphome/components/pm2005/__init__.py +1 -0
  298. esphome/components/pm2005/pm2005.cpp +123 -0
  299. esphome/components/pm2005/pm2005.h +46 -0
  300. esphome/components/pm2005/sensor.py +86 -0
  301. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  302. esphome/components/pmsa003i/pmsa003i.h +25 -25
  303. esphome/components/pmsx003/pmsx003.cpp +195 -230
  304. esphome/components/pmsx003/pmsx003.h +51 -33
  305. esphome/components/pmsx003/sensor.py +21 -11
  306. esphome/components/pn7150/pn7150.h +2 -2
  307. esphome/components/pn7160/pn7160.h +2 -2
  308. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  309. esphome/components/prometheus/prometheus_handler.h +17 -0
  310. esphome/components/psram/__init__.py +7 -5
  311. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  312. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  313. esphome/components/pzem004t/pzem004t.cpp +2 -1
  314. esphome/components/qspi_dbi/__init__.py +0 -1
  315. esphome/components/qspi_dbi/display.py +2 -1
  316. esphome/components/qspi_dbi/models.py +1 -2
  317. esphome/components/remote_base/__init__.py +91 -0
  318. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  319. esphome/components/remote_base/beo4_protocol.h +43 -0
  320. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  321. esphome/components/remote_base/gobox_protocol.h +54 -0
  322. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  323. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  324. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  325. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  326. esphome/components/scd30/sensor.py +2 -3
  327. esphome/components/scd4x/sensor.py +4 -5
  328. esphome/components/sdp3x/sensor.py +2 -1
  329. esphome/components/sds011/sds011.cpp +2 -1
  330. esphome/components/select/__init__.py +19 -20
  331. esphome/components/sen5x/sen5x.cpp +55 -36
  332. esphome/components/sen5x/sensor.py +1 -1
  333. esphome/components/senseair/sensor.py +3 -3
  334. esphome/components/sensor/__init__.py +158 -14
  335. esphome/components/sensor/filter.cpp +23 -0
  336. esphome/components/sensor/filter.h +22 -0
  337. esphome/components/sgp30/sensor.py +14 -16
  338. esphome/components/sgp4x/sensor.py +1 -1
  339. esphome/components/sht4x/sht4x.cpp +43 -22
  340. esphome/components/sht4x/sht4x.h +1 -1
  341. esphome/components/shtcx/sensor.py +6 -6
  342. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  343. esphome/components/sml/text_sensor/__init__.py +4 -6
  344. esphome/components/sound_level/__init__.py +0 -0
  345. esphome/components/sound_level/sensor.py +97 -0
  346. esphome/components/sound_level/sound_level.cpp +194 -0
  347. esphome/components/sound_level/sound_level.h +73 -0
  348. esphome/components/speaker/media_player/__init__.py +4 -8
  349. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  350. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  351. esphome/components/speaker/speaker.h +4 -7
  352. esphome/components/speed/fan/__init__.py +17 -16
  353. esphome/components/spi/spi.h +11 -1
  354. esphome/components/sprinkler/__init__.py +18 -19
  355. esphome/components/sprinkler/sprinkler.cpp +6 -5
  356. esphome/components/switch/__init__.py +32 -42
  357. esphome/components/syslog/__init__.py +41 -0
  358. esphome/components/syslog/esphome_syslog.cpp +49 -0
  359. esphome/components/syslog/esphome_syslog.h +27 -0
  360. esphome/components/t6615/sensor.py +3 -3
  361. esphome/components/t6615/t6615.cpp +2 -1
  362. esphome/components/tca9555/tca9555.cpp +11 -6
  363. esphome/components/tcl112/climate.py +2 -9
  364. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  365. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  366. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  367. esphome/components/template/cover/__init__.py +27 -21
  368. esphome/components/template/fan/__init__.py +14 -12
  369. esphome/components/template/lock/__init__.py +20 -25
  370. esphome/components/template/lock/automation.h +18 -0
  371. esphome/components/template/text/__init__.py +4 -3
  372. esphome/components/template/valve/__init__.py +32 -21
  373. esphome/components/template/valve/automation.h +24 -0
  374. esphome/components/text/__init__.py +32 -1
  375. esphome/components/text_sensor/__init__.py +24 -29
  376. esphome/components/thermostat/climate.py +5 -5
  377. esphome/components/time_based/cover.py +17 -16
  378. esphome/components/time_based/time_based_cover.cpp +2 -1
  379. esphome/components/tm1638/switch/__init__.py +10 -7
  380. esphome/components/tormatic/cover.py +4 -5
  381. esphome/components/toshiba/climate.py +3 -5
  382. esphome/components/touchscreen/touchscreen.cpp +3 -1
  383. esphome/components/tt21100/touchscreen/tt21100.cpp +1 -1
  384. esphome/components/tuya/climate/__init__.py +5 -6
  385. esphome/components/tuya/cover/__init__.py +6 -11
  386. esphome/components/tuya/select/__init__.py +15 -5
  387. esphome/components/tuya/select/tuya_select.cpp +6 -1
  388. esphome/components/tuya/select/tuya_select.h +5 -1
  389. esphome/components/uart/packet_transport/__init__.py +20 -0
  390. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  391. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  392. esphome/components/uart/switch/uart_switch.cpp +2 -1
  393. esphome/components/udp/__init__.py +126 -128
  394. esphome/components/udp/automation.h +40 -0
  395. esphome/components/udp/binary_sensor.py +3 -25
  396. esphome/components/udp/packet_transport/__init__.py +29 -0
  397. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  398. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  399. esphome/components/udp/sensor.py +3 -25
  400. esphome/components/udp/udp_component.cpp +26 -470
  401. esphome/components/udp/udp_component.h +21 -128
  402. esphome/components/update/__init__.py +31 -1
  403. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  404. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  405. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  406. esphome/components/uptime/text_sensor/__init__.py +47 -7
  407. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  408. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  409. esphome/components/valve/__init__.py +34 -3
  410. esphome/components/valve/automation.h +1 -19
  411. esphome/components/vl53l0x/sensor.py +11 -0
  412. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  413. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  414. esphome/components/voice_assistant/__init__.py +36 -10
  415. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  416. esphome/components/voice_assistant/voice_assistant.h +26 -25
  417. esphome/components/waveshare_epaper/display.py +6 -0
  418. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  419. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  420. esphome/components/weikai/weikai.cpp +0 -52
  421. esphome/components/whirlpool/climate.py +3 -5
  422. esphome/components/whynter/climate.py +3 -5
  423. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  424. esphome/components/yashima/climate.py +6 -6
  425. esphome/components/zhlt01/climate.py +2 -7
  426. esphome/config.py +13 -13
  427. esphome/config_validation.py +38 -58
  428. esphome/const.py +15 -1
  429. esphome/core/__init__.py +2 -0
  430. esphome/core/application.cpp +27 -10
  431. esphome/core/application.h +9 -1
  432. esphome/core/automation.h +4 -3
  433. esphome/core/component.cpp +28 -7
  434. esphome/core/component.h +10 -1
  435. esphome/core/defines.h +23 -17
  436. esphome/core/doxygen.h +13 -0
  437. esphome/core/macros.h +4 -0
  438. esphome/core/scheduler.cpp +7 -1
  439. esphome/cpp_generator.py +6 -2
  440. esphome/dashboard/web_server.py +3 -3
  441. esphome/helpers.py +39 -0
  442. esphome/loader.py +4 -0
  443. esphome/log.py +15 -19
  444. esphome/mqtt.py +23 -10
  445. esphome/platformio_api.py +1 -1
  446. esphome/schema_extractors.py +0 -1
  447. esphome/voluptuous_schema.py +3 -1
  448. esphome/vscode.py +15 -0
  449. esphome/wizard.py +47 -37
  450. esphome/zeroconf.py +7 -3
  451. {esphome-2025.4.1.dist-info → esphome-2025.5.0.dist-info}/METADATA +10 -11
  452. {esphome-2025.4.1.dist-info → esphome-2025.5.0.dist-info}/RECORD +456 -396
  453. {esphome-2025.4.1.dist-info → esphome-2025.5.0.dist-info}/WHEEL +1 -1
  454. esphome/components/esp32_ble/const_esp32c6.h +0 -74
  455. {esphome-2025.4.1.dist-info → esphome-2025.5.0.dist-info}/entry_points.txt +0 -0
  456. {esphome-2025.4.1.dist-info → esphome-2025.5.0.dist-info}/licenses/LICENSE +0 -0
  457. {esphome-2025.4.1.dist-info → esphome-2025.5.0.dist-info}/top_level.txt +0 -0
@@ -31,8 +31,7 @@ def esp32_validate_gpio_pin(value):
31
31
  )
32
32
  if 9 <= value <= 10:
33
33
  _LOGGER.warning(
34
- "Pin %s (9-10) might already be used by the "
35
- "flash interface in QUAD IO flash mode.",
34
+ "Pin %s (9-10) might already be used by the flash interface in QUAD IO flash mode.",
36
35
  value,
37
36
  )
38
37
  if value in (24, 28, 29, 30, 31):
@@ -22,7 +22,7 @@ def esp32_c2_validate_supports(value):
22
22
  is_input = mode[CONF_INPUT]
23
23
 
24
24
  if num < 0 or num > 20:
25
- raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)")
25
+ raise cv.Invalid(f"Invalid pin number: {num} (must be 0-20)")
26
26
 
27
27
  if is_input:
28
28
  # All ESP32 pins support input mode
@@ -35,7 +35,7 @@ def esp32_c3_validate_supports(value):
35
35
  is_input = mode[CONF_INPUT]
36
36
 
37
37
  if num < 0 or num > 21:
38
- raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
38
+ raise cv.Invalid(f"Invalid pin number: {num} (must be 0-21)")
39
39
 
40
40
  if is_input:
41
41
  # All ESP32 pins support input mode
@@ -36,7 +36,7 @@ def esp32_c6_validate_supports(value):
36
36
  is_input = mode[CONF_INPUT]
37
37
 
38
38
  if num < 0 or num > 23:
39
- raise cv.Invalid(f"Invalid pin number: {value} (must be 0-23)")
39
+ raise cv.Invalid(f"Invalid pin number: {num} (must be 0-23)")
40
40
  if is_input:
41
41
  # All ESP32 pins support input mode
42
42
  pass
@@ -45,7 +45,7 @@ def esp32_h2_validate_supports(value):
45
45
  is_input = mode[CONF_INPUT]
46
46
 
47
47
  if num < 0 or num > 27:
48
- raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
48
+ raise cv.Invalid(f"Invalid pin number: {num} (must be 0-27)")
49
49
  if is_input:
50
50
  # All ESP32 pins support input mode
51
51
  pass
@@ -2,10 +2,6 @@
2
2
 
3
3
  #include "ble.h"
4
4
 
5
- #ifdef USE_ESP32_VARIANT_ESP32C6
6
- #include "const_esp32c6.h"
7
- #endif // USE_ESP32_VARIANT_ESP32C6
8
-
9
5
  #include "esphome/core/application.h"
10
6
  #include "esphome/core/log.h"
11
7
 
@@ -114,6 +110,7 @@ void ESP32BLE::advertising_init_() {
114
110
 
115
111
  this->advertising_->set_scan_response(true);
116
112
  this->advertising_->set_min_preferred_interval(0x06);
113
+ this->advertising_->set_appearance(this->appearance_);
117
114
  }
118
115
 
119
116
  bool ESP32BLE::ble_setup_() {
@@ -127,11 +124,7 @@ bool ESP32BLE::ble_setup_() {
127
124
  if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
128
125
  // start bt controller
129
126
  if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
130
- #ifdef USE_ESP32_VARIANT_ESP32C6
131
- esp_bt_controller_config_t cfg = BT_CONTROLLER_CONFIG;
132
- #else
133
127
  esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
134
- #endif
135
128
  err = esp_bt_controller_init(&cfg);
136
129
  if (err != ESP_OK) {
137
130
  ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
@@ -95,6 +95,7 @@ class ESP32BLE : public Component {
95
95
  void advertising_start();
96
96
  void advertising_set_service_data(const std::vector<uint8_t> &data);
97
97
  void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
98
+ void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
98
99
  void advertising_add_service_uuid(ESPBTUUID uuid);
99
100
  void advertising_remove_service_uuid(ESPBTUUID uuid);
100
101
  void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
@@ -128,11 +129,12 @@ class ESP32BLE : public Component {
128
129
  BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
129
130
 
130
131
  Queue<BLEEvent> ble_events_;
131
- BLEAdvertising *advertising_;
132
+ BLEAdvertising *advertising_{};
132
133
  esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
133
- uint32_t advertising_cycle_time_;
134
- bool enable_on_boot_;
134
+ uint32_t advertising_cycle_time_{};
135
+ bool enable_on_boot_{};
135
136
  optional<std::string> name_;
137
+ uint16_t appearance_{0};
136
138
  };
137
139
 
138
140
  // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
@@ -6,6 +6,7 @@
6
6
  #include <cstring>
7
7
  #include "ble_uuid.h"
8
8
  #include "esphome/core/log.h"
9
+ #include "esphome/core/application.h"
9
10
 
10
11
  namespace esphome {
11
12
  namespace esp32_ble {
@@ -143,7 +144,7 @@ void BLEAdvertising::loop() {
143
144
  if (this->raw_advertisements_callbacks_.empty()) {
144
145
  return;
145
146
  }
146
- const uint32_t now = millis();
147
+ const uint32_t now = App.get_loop_component_start_time();
147
148
  if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
148
149
  this->stop();
149
150
  this->current_adv_index_ += 1;
@@ -32,6 +32,7 @@ class BLEAdvertising {
32
32
  void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
33
33
  void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
34
34
  void set_manufacturer_data(const std::vector<uint8_t> &data);
35
+ void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
35
36
  void set_service_data(const std::vector<uint8_t> &data);
36
37
  void register_raw_advertisement_callback(std::function<void(bool)> &&callback);
37
38
 
@@ -32,6 +32,7 @@ DEPENDENCIES = ["esp32"]
32
32
  DOMAIN = "esp32_ble_server"
33
33
 
34
34
  CONF_ADVERTISE = "advertise"
35
+ CONF_APPEARANCE = "appearance"
35
36
  CONF_BROADCAST = "broadcast"
36
37
  CONF_CHARACTERISTICS = "characteristics"
37
38
  CONF_DESCRIPTION = "description"
@@ -421,6 +422,7 @@ CONFIG_SCHEMA = cv.Schema(
421
422
  cv.GenerateID(): cv.declare_id(BLEServer),
422
423
  cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
423
424
  cv.Optional(CONF_MANUFACTURER): value_schema("string", templatable=False),
425
+ cv.Optional(CONF_APPEARANCE, default=0): cv.uint16_t,
424
426
  cv.Optional(CONF_MODEL): value_schema("string", templatable=False),
425
427
  cv.Optional(CONF_FIRMWARE_VERSION): value_schema("string", templatable=False),
426
428
  cv.Optional(CONF_MANUFACTURER_DATA): cv.Schema([cv.uint8_t]),
@@ -531,6 +533,7 @@ async def to_code(config):
531
533
  cg.add(parent.register_gatts_event_handler(var))
532
534
  cg.add(parent.register_ble_status_event_handler(var))
533
535
  cg.add(var.set_parent(parent))
536
+ cg.add(parent.advertising_set_appearance(config[CONF_APPEARANCE]))
534
537
  if CONF_MANUFACTURER_DATA in config:
535
538
  cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA]))
536
539
  for service_config in config[CONF_SERVICES]:
@@ -17,6 +17,7 @@ from esphome.components.esp32_ble import (
17
17
  import esphome.config_validation as cv
18
18
  from esphome.const import (
19
19
  CONF_ACTIVE,
20
+ CONF_CONTINUOUS,
20
21
  CONF_DURATION,
21
22
  CONF_ID,
22
23
  CONF_INTERVAL,
@@ -42,8 +43,8 @@ CONF_MAX_CONNECTIONS = "max_connections"
42
43
  CONF_ESP32_BLE_ID = "esp32_ble_id"
43
44
  CONF_SCAN_PARAMETERS = "scan_parameters"
44
45
  CONF_WINDOW = "window"
45
- CONF_CONTINUOUS = "continuous"
46
46
  CONF_ON_SCAN_END = "on_scan_end"
47
+ CONF_SOFTWARE_COEXISTENCE = "software_coexistence"
47
48
 
48
49
  DEFAULT_MAX_CONNECTIONS = 3
49
50
  IDF_MAX_CONNECTIONS = 9
@@ -203,6 +204,7 @@ CONFIG_SCHEMA = cv.All(
203
204
  cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
204
205
  {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
205
206
  ),
207
+ cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool,
206
208
  }
207
209
  ).extend(cv.COMPONENT_SCHEMA),
208
210
  )
@@ -310,6 +312,8 @@ async def to_code(config):
310
312
 
311
313
  if CORE.using_esp_idf:
312
314
  add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
315
+ if config.get(CONF_SOFTWARE_COEXISTENCE):
316
+ add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
313
317
  # https://github.com/espressif/esp-idf/issues/4101
314
318
  # https://github.com/espressif/esp-idf/issues/2503
315
319
  # Match arduino CONFIG_BTU_TASK_STACK_SIZE
@@ -331,6 +335,8 @@ async def to_code(config):
331
335
 
332
336
  cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
333
337
  cg.add_define("USE_ESP32_BLE_CLIENT")
338
+ if config.get(CONF_SOFTWARE_COEXISTENCE):
339
+ cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE")
334
340
 
335
341
 
336
342
  ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
@@ -21,6 +21,10 @@
21
21
  #include "esphome/components/ota/ota_backend.h"
22
22
  #endif
23
23
 
24
+ #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
25
+ #include <esp_coexist.h>
26
+ #endif
27
+
24
28
  #ifdef USE_ARDUINO
25
29
  #include <esp32-hal-bt.h>
26
30
  #endif
@@ -57,7 +61,6 @@ void ESP32BLETracker::setup() {
57
61
 
58
62
  global_esp32_ble_tracker = this;
59
63
  this->scan_result_lock_ = xSemaphoreCreateMutex();
60
- this->scan_end_lock_ = xSemaphoreCreateMutex();
61
64
 
62
65
  #ifdef USE_OTA
63
66
  ota::get_global_ota_callback()->add_on_state_callback(
@@ -117,119 +120,119 @@ void ESP32BLETracker::loop() {
117
120
  }
118
121
  bool promote_to_connecting = discovered && !searching && !connecting;
119
122
 
120
- if (!this->scanner_idle_) {
121
- if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
122
- xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
123
- uint32_t index = this->scan_result_index_;
124
- if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
125
- ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
126
- }
123
+ if (this->scanner_state_ == ScannerState::RUNNING &&
124
+ this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
125
+ xSemaphoreTake(this->scan_result_lock_, 0)) {
126
+ uint32_t index = this->scan_result_index_;
127
+ if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
128
+ ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
129
+ }
127
130
 
128
- if (this->raw_advertisements_) {
129
- for (auto *listener : this->listeners_) {
130
- listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
131
- }
132
- for (auto *client : this->clients_) {
133
- client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
134
- }
131
+ if (this->raw_advertisements_) {
132
+ for (auto *listener : this->listeners_) {
133
+ listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
135
134
  }
135
+ for (auto *client : this->clients_) {
136
+ client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
137
+ }
138
+ }
136
139
 
137
- if (this->parse_advertisements_) {
138
- for (size_t i = 0; i < index; i++) {
139
- ESPBTDevice device;
140
- device.parse_scan_rst(this->scan_result_buffer_[i]);
140
+ if (this->parse_advertisements_) {
141
+ for (size_t i = 0; i < index; i++) {
142
+ ESPBTDevice device;
143
+ device.parse_scan_rst(this->scan_result_buffer_[i]);
141
144
 
142
- bool found = false;
143
- for (auto *listener : this->listeners_) {
144
- if (listener->parse_device(device))
145
- found = true;
146
- }
145
+ bool found = false;
146
+ for (auto *listener : this->listeners_) {
147
+ if (listener->parse_device(device))
148
+ found = true;
149
+ }
147
150
 
148
- for (auto *client : this->clients_) {
149
- if (client->parse_device(device)) {
150
- found = true;
151
- if (!connecting && client->state() == ClientState::DISCOVERED) {
152
- promote_to_connecting = true;
153
- }
151
+ for (auto *client : this->clients_) {
152
+ if (client->parse_device(device)) {
153
+ found = true;
154
+ if (!connecting && client->state() == ClientState::DISCOVERED) {
155
+ promote_to_connecting = true;
154
156
  }
155
157
  }
158
+ }
156
159
 
157
- if (!found && !this->scan_continuous_) {
158
- this->print_bt_device_info(device);
159
- }
160
+ if (!found && !this->scan_continuous_) {
161
+ this->print_bt_device_info(device);
160
162
  }
161
163
  }
162
- this->scan_result_index_ = 0;
163
- xSemaphoreGive(this->scan_result_lock_);
164
164
  }
165
+ this->scan_result_index_ = 0;
166
+ xSemaphoreGive(this->scan_result_lock_);
167
+ }
168
+ if (this->scanner_state_ == ScannerState::STOPPED) {
169
+ this->end_of_scan_(); // Change state to IDLE
170
+ }
171
+ if (this->scanner_state_ == ScannerState::FAILED ||
172
+ (this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
173
+ this->stop_scan_();
174
+ if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
175
+ ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
176
+ std::numeric_limits<uint8_t>::max());
177
+ App.reboot();
178
+ }
179
+ if (this->scan_start_failed_) {
180
+ ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
181
+ this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
182
+ }
183
+ if (this->scan_set_param_failed_) {
184
+ ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
185
+ this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
186
+ }
187
+ }
188
+ /*
165
189
 
166
- /*
167
-
168
- Avoid starting the scanner if:
169
- - we are already scanning
170
- - we are connecting to a device
171
- - we are disconnecting from a device
190
+ Avoid starting the scanner if:
191
+ - we are already scanning
192
+ - we are connecting to a device
193
+ - we are disconnecting from a device
172
194
 
173
- Otherwise the scanner could fail to ever start again
174
- and our only way to recover is to reboot.
195
+ Otherwise the scanner could fail to ever start again
196
+ and our only way to recover is to reboot.
175
197
 
176
- https://github.com/espressif/esp-idf/issues/6688
198
+ https://github.com/espressif/esp-idf/issues/6688
177
199
 
178
- */
179
- if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) {
180
- if (this->scan_continuous_) {
181
- if (!disconnecting && !promote_to_connecting && !this->scan_start_failed_ && !this->scan_set_param_failed_) {
182
- this->start_scan_(false);
183
- } else {
184
- // We didn't start the scan, so we need to release the lock
185
- xSemaphoreGive(this->scan_end_lock_);
186
- }
187
- } else if (!this->scanner_idle_) {
188
- this->end_of_scan_();
189
- return;
190
- }
200
+ */
201
+ if (this->scanner_state_ == ScannerState::IDLE && !connecting && !disconnecting && !promote_to_connecting) {
202
+ #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
203
+ if (this->coex_prefer_ble_) {
204
+ this->coex_prefer_ble_ = false;
205
+ ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
206
+ esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
191
207
  }
192
-
193
- if (this->scan_start_failed_ || this->scan_set_param_failed_) {
194
- if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
195
- ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
196
- std::numeric_limits<uint8_t>::max());
197
- App.reboot();
198
- }
199
- if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
200
- xSemaphoreGive(this->scan_end_lock_);
201
- } else {
202
- ESP_LOGD(TAG, "Stopping scan after failure...");
203
- this->stop_scan_();
204
- }
205
- if (this->scan_start_failed_) {
206
- ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
207
- this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
208
- }
209
- if (this->scan_set_param_failed_) {
210
- ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
211
- this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
212
- }
208
+ #endif
209
+ if (this->scan_continuous_) {
210
+ this->start_scan_(false); // first = false
213
211
  }
214
212
  }
215
-
216
213
  // If there is a discovered client and no connecting
217
214
  // clients and no clients using the scanner to search for
218
215
  // devices, then stop scanning and promote the discovered
219
216
  // client to ready to connect.
220
- if (promote_to_connecting) {
217
+ if (promote_to_connecting &&
218
+ (this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
221
219
  for (auto *client : this->clients_) {
222
220
  if (client->state() == ClientState::DISCOVERED) {
223
- if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
224
- // Scanner is not running since we got the
225
- // lock, so we can promote the client.
226
- xSemaphoreGive(this->scan_end_lock_);
221
+ if (this->scanner_state_ == ScannerState::RUNNING) {
222
+ ESP_LOGD(TAG, "Stopping scan to make connection...");
223
+ this->stop_scan_();
224
+ } else if (this->scanner_state_ == ScannerState::IDLE) {
225
+ ESP_LOGD(TAG, "Promoting client to connect...");
227
226
  // We only want to promote one client at a time.
228
227
  // once the scanner is fully stopped.
228
+ #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
229
+ ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
230
+ if (!this->coex_prefer_ble_) {
231
+ this->coex_prefer_ble_ = true;
232
+ esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
233
+ }
234
+ #endif
229
235
  client->set_state(ClientState::READY_TO_CONNECT);
230
- } else {
231
- ESP_LOGD(TAG, "Pausing scan to make connection...");
232
- this->stop_scan_();
233
236
  }
234
237
  break;
235
238
  }
@@ -237,13 +240,7 @@ void ESP32BLETracker::loop() {
237
240
  }
238
241
  }
239
242
 
240
- void ESP32BLETracker::start_scan() {
241
- if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
242
- this->start_scan_(true);
243
- } else {
244
- ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring.");
245
- }
246
- }
243
+ void ESP32BLETracker::start_scan() { this->start_scan_(true); }
247
244
 
248
245
  void ESP32BLETracker::stop_scan() {
249
246
  ESP_LOGD(TAG, "Stopping scan.");
@@ -251,16 +248,23 @@ void ESP32BLETracker::stop_scan() {
251
248
  this->stop_scan_();
252
249
  }
253
250
 
254
- void ESP32BLETracker::ble_before_disabled_event_handler() {
255
- this->stop_scan_();
256
- xSemaphoreGive(this->scan_end_lock_);
257
- }
251
+ void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); }
258
252
 
259
253
  void ESP32BLETracker::stop_scan_() {
260
- this->cancel_timeout("scan");
261
- if (this->scanner_idle_) {
254
+ if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
255
+ if (this->scanner_state_ == ScannerState::IDLE) {
256
+ ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
257
+ } else if (this->scanner_state_ == ScannerState::STARTING) {
258
+ ESP_LOGE(TAG, "Scan is starting while trying to stop.");
259
+ } else if (this->scanner_state_ == ScannerState::STOPPING) {
260
+ ESP_LOGE(TAG, "Scan is already stopping while trying to stop.");
261
+ } else if (this->scanner_state_ == ScannerState::STOPPED) {
262
+ ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
263
+ }
262
264
  return;
263
265
  }
266
+ this->cancel_timeout("scan");
267
+ this->set_scanner_state_(ScannerState::STOPPING);
264
268
  esp_err_t err = esp_ble_gap_stop_scanning();
265
269
  if (err != ESP_OK) {
266
270
  ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
@@ -273,13 +277,22 @@ void ESP32BLETracker::start_scan_(bool first) {
273
277
  ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
274
278
  return;
275
279
  }
276
- // The lock must be held when calling this function.
277
- if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
278
- ESP_LOGE(TAG, "start_scan called without holding scan_end_lock_");
280
+ if (this->scanner_state_ != ScannerState::IDLE) {
281
+ if (this->scanner_state_ == ScannerState::STARTING) {
282
+ ESP_LOGE(TAG, "Cannot start scan while already starting.");
283
+ } else if (this->scanner_state_ == ScannerState::RUNNING) {
284
+ ESP_LOGE(TAG, "Cannot start scan while already running.");
285
+ } else if (this->scanner_state_ == ScannerState::STOPPING) {
286
+ ESP_LOGE(TAG, "Cannot start scan while already stopping.");
287
+ } else if (this->scanner_state_ == ScannerState::FAILED) {
288
+ ESP_LOGE(TAG, "Cannot start scan while already failed.");
289
+ } else if (this->scanner_state_ == ScannerState::STOPPED) {
290
+ ESP_LOGE(TAG, "Cannot start scan while already stopped.");
291
+ }
279
292
  return;
280
293
  }
281
-
282
- ESP_LOGD(TAG, "Starting scan...");
294
+ this->set_scanner_state_(ScannerState::STARTING);
295
+ ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
283
296
  if (!first) {
284
297
  for (auto *listener : this->listeners_)
285
298
  listener->on_scan_end();
@@ -307,24 +320,21 @@ void ESP32BLETracker::start_scan_(bool first) {
307
320
  ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
308
321
  return;
309
322
  }
310
- this->scanner_idle_ = false;
311
323
  }
312
324
 
313
325
  void ESP32BLETracker::end_of_scan_() {
314
326
  // The lock must be held when calling this function.
315
- if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
316
- ESP_LOGE(TAG, "end_of_scan_ called without holding the scan_end_lock_");
327
+ if (this->scanner_state_ != ScannerState::STOPPED) {
328
+ ESP_LOGE(TAG, "end_of_scan_ called while scanner is not stopped.");
317
329
  return;
318
330
  }
319
-
320
- ESP_LOGD(TAG, "End of scan.");
321
- this->scanner_idle_ = true;
331
+ ESP_LOGD(TAG, "End of scan, set scanner state to IDLE.");
322
332
  this->already_discovered_.clear();
323
- xSemaphoreGive(this->scan_end_lock_);
324
333
  this->cancel_timeout("scan");
325
334
 
326
335
  for (auto *listener : this->listeners_)
327
336
  listener->on_scan_end();
337
+ this->set_scanner_state_(ScannerState::IDLE);
328
338
  }
329
339
 
330
340
  void ESP32BLETracker::register_client(ESPBTClient *client) {
@@ -392,32 +402,73 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t:
392
402
  void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
393
403
  ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
394
404
  this->scan_start_failed_ = param.status;
405
+ if (this->scanner_state_ != ScannerState::STARTING) {
406
+ if (this->scanner_state_ == ScannerState::RUNNING) {
407
+ ESP_LOGE(TAG, "Scan was already running when start complete.");
408
+ } else if (this->scanner_state_ == ScannerState::STOPPING) {
409
+ ESP_LOGE(TAG, "Scan was stopping when start complete.");
410
+ } else if (this->scanner_state_ == ScannerState::FAILED) {
411
+ ESP_LOGE(TAG, "Scan was in failed state when start complete.");
412
+ } else if (this->scanner_state_ == ScannerState::IDLE) {
413
+ ESP_LOGE(TAG, "Scan was idle when start complete.");
414
+ } else if (this->scanner_state_ == ScannerState::STOPPED) {
415
+ ESP_LOGE(TAG, "Scan was stopped when start complete.");
416
+ }
417
+ }
395
418
  if (param.status == ESP_BT_STATUS_SUCCESS) {
396
419
  this->scan_start_fail_count_ = 0;
420
+ this->set_scanner_state_(ScannerState::RUNNING);
397
421
  } else {
422
+ this->set_scanner_state_(ScannerState::FAILED);
398
423
  if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
399
424
  this->scan_start_fail_count_++;
400
425
  }
401
- xSemaphoreGive(this->scan_end_lock_);
402
426
  }
403
427
  }
404
428
 
405
429
  void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
406
430
  ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
407
- xSemaphoreGive(this->scan_end_lock_);
431
+ if (this->scanner_state_ != ScannerState::STOPPING) {
432
+ if (this->scanner_state_ == ScannerState::RUNNING) {
433
+ ESP_LOGE(TAG, "Scan was not running when stop complete.");
434
+ } else if (this->scanner_state_ == ScannerState::STARTING) {
435
+ ESP_LOGE(TAG, "Scan was not started when stop complete.");
436
+ } else if (this->scanner_state_ == ScannerState::FAILED) {
437
+ ESP_LOGE(TAG, "Scan was in failed state when stop complete.");
438
+ } else if (this->scanner_state_ == ScannerState::IDLE) {
439
+ ESP_LOGE(TAG, "Scan was idle when stop complete.");
440
+ } else if (this->scanner_state_ == ScannerState::STOPPED) {
441
+ ESP_LOGE(TAG, "Scan was stopped when stop complete.");
442
+ }
443
+ }
444
+ this->set_scanner_state_(ScannerState::STOPPED);
408
445
  }
409
446
 
410
447
  void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
411
448
  ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt);
412
449
  if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
413
- if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
450
+ if (xSemaphoreTake(this->scan_result_lock_, 0)) {
414
451
  if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
415
452
  this->scan_result_buffer_[this->scan_result_index_++] = param;
416
453
  }
417
454
  xSemaphoreGive(this->scan_result_lock_);
418
455
  }
419
456
  } else if (param.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
420
- xSemaphoreGive(this->scan_end_lock_);
457
+ // Scan finished on its own
458
+ if (this->scanner_state_ != ScannerState::RUNNING) {
459
+ if (this->scanner_state_ == ScannerState::STOPPING) {
460
+ ESP_LOGE(TAG, "Scan was not running when scan completed.");
461
+ } else if (this->scanner_state_ == ScannerState::STARTING) {
462
+ ESP_LOGE(TAG, "Scan was not started when scan completed.");
463
+ } else if (this->scanner_state_ == ScannerState::FAILED) {
464
+ ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
465
+ } else if (this->scanner_state_ == ScannerState::IDLE) {
466
+ ESP_LOGE(TAG, "Scan was idle when scan completed.");
467
+ } else if (this->scanner_state_ == ScannerState::STOPPED) {
468
+ ESP_LOGE(TAG, "Scan was stopped when scan completed.");
469
+ }
470
+ }
471
+ this->set_scanner_state_(ScannerState::STOPPED);
421
472
  }
422
473
  }
423
474
 
@@ -428,6 +479,11 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
428
479
  }
429
480
  }
430
481
 
482
+ void ESP32BLETracker::set_scanner_state_(ScannerState state) {
483
+ this->scanner_state_ = state;
484
+ this->scanner_state_callbacks_.call(state);
485
+ }
486
+
431
487
  ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
432
488
  optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
433
489
  if (!data.uuid.contains(0x4C, 0x00))
@@ -680,8 +736,26 @@ void ESP32BLETracker::dump_config() {
680
736
  ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
681
737
  ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");
682
738
  ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", YESNO(this->scan_continuous_));
683
- ESP_LOGCONFIG(TAG, " Scanner Idle: %s", YESNO(this->scanner_idle_));
684
- ESP_LOGCONFIG(TAG, " Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr));
739
+ switch (this->scanner_state_) {
740
+ case ScannerState::IDLE:
741
+ ESP_LOGCONFIG(TAG, " Scanner State: IDLE");
742
+ break;
743
+ case ScannerState::STARTING:
744
+ ESP_LOGCONFIG(TAG, " Scanner State: STARTING");
745
+ break;
746
+ case ScannerState::RUNNING:
747
+ ESP_LOGCONFIG(TAG, " Scanner State: RUNNING");
748
+ break;
749
+ case ScannerState::STOPPING:
750
+ ESP_LOGCONFIG(TAG, " Scanner State: STOPPING");
751
+ break;
752
+ case ScannerState::STOPPED:
753
+ ESP_LOGCONFIG(TAG, " Scanner State: STOPPED");
754
+ break;
755
+ case ScannerState::FAILED:
756
+ ESP_LOGCONFIG(TAG, " Scanner State: FAILED");
757
+ break;
758
+ }
685
759
  ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
686
760
  searching_, disconnecting_);
687
761
  if (this->scan_start_fail_count_) {