esphome 2025.4.1__py3-none-any.whl → 2025.5.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
  2. esphome/components/adc/__init__.py +51 -34
  3. esphome/components/airthings_wave_base/__init__.py +1 -1
  4. esphome/components/alarm_control_panel/__init__.py +37 -2
  5. esphome/components/am43/cover/__init__.py +4 -5
  6. esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
  7. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
  8. esphome/components/analog_threshold/binary_sensor.py +10 -8
  9. esphome/components/anova/climate.py +4 -5
  10. esphome/components/api/__init__.py +25 -8
  11. esphome/components/api/api_connection.cpp +77 -10
  12. esphome/components/api/api_connection.h +6 -1
  13. esphome/components/api/api_frame_helper.cpp +98 -130
  14. esphome/components/api/api_frame_helper.h +12 -2
  15. esphome/components/api/api_noise_context.h +13 -4
  16. esphome/components/api/api_pb2.cpp +1422 -1
  17. esphome/components/api/api_pb2.h +255 -1
  18. esphome/components/api/api_pb2_service.cpp +162 -49
  19. esphome/components/api/api_pb2_service.h +90 -51
  20. esphome/components/api/api_pb2_size.h +361 -0
  21. esphome/components/api/api_server.cpp +110 -34
  22. esphome/components/api/api_server.h +8 -0
  23. esphome/components/api/proto.h +38 -9
  24. esphome/components/as3935_i2c/as3935_i2c.h +0 -3
  25. esphome/components/as7341/as7341.h +1 -1
  26. esphome/components/atm90e32/__init__.py +1 -0
  27. esphome/components/atm90e32/atm90e32.cpp +576 -199
  28. esphome/components/atm90e32/atm90e32.h +128 -31
  29. esphome/components/atm90e32/atm90e32_reg.h +4 -2
  30. esphome/components/atm90e32/button/__init__.py +62 -10
  31. esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
  32. esphome/components/atm90e32/button/atm90e32_button.h +36 -4
  33. esphome/components/atm90e32/number/__init__.py +130 -0
  34. esphome/components/atm90e32/number/atm90e32_number.h +16 -0
  35. esphome/components/atm90e32/sensor.py +21 -4
  36. esphome/components/atm90e32/text_sensor/__init__.py +48 -0
  37. esphome/components/audio/__init__.py +96 -49
  38. esphome/components/audio/audio.h +48 -0
  39. esphome/components/audio/audio_decoder.cpp +1 -1
  40. esphome/components/audio/audio_resampler.cpp +2 -0
  41. esphome/components/audio/audio_resampler.h +1 -0
  42. esphome/components/ballu/climate.py +2 -9
  43. esphome/components/bang_bang/climate.py +5 -6
  44. esphome/components/bedjet/climate/__init__.py +3 -8
  45. esphome/components/bedjet/fan/__init__.py +2 -11
  46. esphome/components/binary/fan/__init__.py +13 -16
  47. esphome/components/binary_sensor/__init__.py +13 -10
  48. esphome/components/binary_sensor/binary_sensor.cpp +6 -10
  49. esphome/components/binary_sensor/binary_sensor.h +1 -1
  50. esphome/components/binary_sensor/filter.cpp +21 -21
  51. esphome/components/binary_sensor/filter.h +10 -10
  52. esphome/components/bl0906/constants.h +16 -16
  53. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  54. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  55. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +135 -21
  56. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  57. esphome/components/button/__init__.py +11 -8
  58. esphome/components/canbus/canbus.cpp +3 -0
  59. esphome/components/canbus/canbus.h +16 -0
  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/cst226/binary_sensor/__init__.py +28 -0
  76. esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
  77. esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
  78. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
  79. esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
  80. esphome/components/current_based/cover.py +37 -36
  81. esphome/components/daikin/climate.py +2 -9
  82. esphome/components/daikin/daikin.cpp +15 -9
  83. esphome/components/daikin/daikin.h +5 -5
  84. esphome/components/daikin_arc/climate.py +2 -7
  85. esphome/components/daikin_brc/climate.py +3 -5
  86. esphome/components/dallas_temp/dallas_temp.cpp +17 -24
  87. esphome/components/dallas_temp/dallas_temp.h +0 -1
  88. esphome/components/debug/debug_component.cpp +5 -0
  89. esphome/components/debug/debug_component.h +6 -0
  90. esphome/components/debug/debug_esp32.cpp +109 -254
  91. esphome/components/debug/sensor.py +14 -0
  92. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  93. esphome/components/delonghi/climate.py +2 -9
  94. esphome/components/demo/__init__.py +18 -20
  95. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  96. esphome/components/display/rect.cpp +4 -9
  97. esphome/components/display/rect.h +1 -1
  98. esphome/components/emmeti/climate.py +2 -9
  99. esphome/components/endstop/cover.py +17 -16
  100. esphome/components/esp32/__init__.py +60 -3
  101. esphome/components/esp32/core.cpp +11 -5
  102. esphome/components/esp32/gpio.cpp +86 -24
  103. esphome/components/esp32/gpio.py +15 -16
  104. esphome/components/esp32/gpio_esp32.py +1 -2
  105. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  106. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  107. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  108. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  109. esphome/components/esp32_ble/ble.cpp +1 -8
  110. esphome/components/esp32_ble/ble.h +5 -3
  111. esphome/components/esp32_ble/ble_advertising.h +1 -0
  112. esphome/components/esp32_ble_server/__init__.py +3 -0
  113. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  114. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  115. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  116. esphome/components/esp32_can/esp32_can.cpp +1 -1
  117. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  118. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  119. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  120. esphome/components/esp8266/gpio.cpp +69 -8
  121. esphome/components/event/__init__.py +13 -10
  122. esphome/components/factory_reset/switch/__init__.py +7 -21
  123. esphome/components/fan/__init__.py +52 -5
  124. esphome/components/fastled_base/__init__.py +1 -4
  125. esphome/components/fastled_base/fastled_light.cpp +1 -1
  126. esphome/components/feedback/cover.py +38 -33
  127. esphome/components/fujitsu_general/climate.py +2 -9
  128. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  129. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  130. esphome/components/gpio_expander/cached_gpio.h +22 -7
  131. esphome/components/gps/__init__.py +11 -2
  132. esphome/components/gps/gps.cpp +11 -8
  133. esphome/components/gps/gps.h +9 -6
  134. esphome/components/graph/__init__.py +1 -2
  135. esphome/components/gree/climate.py +4 -6
  136. esphome/components/gree/gree.cpp +16 -2
  137. esphome/components/gree/gree.h +2 -2
  138. esphome/components/haier/climate.py +37 -34
  139. esphome/components/hbridge/fan/__init__.py +19 -17
  140. esphome/components/he60r/cover.py +4 -5
  141. esphome/components/heatpumpir/climate.py +3 -6
  142. esphome/components/hitachi_ac344/climate.py +2 -9
  143. esphome/components/hitachi_ac424/climate.py +2 -9
  144. esphome/components/hlw8012/hlw8012.cpp +1 -1
  145. esphome/components/hm3301/hm3301.h +1 -1
  146. esphome/components/http_request/__init__.py +39 -6
  147. esphome/components/http_request/http_request.cpp +20 -0
  148. esphome/components/http_request/http_request.h +57 -15
  149. esphome/components/http_request/http_request_arduino.cpp +22 -6
  150. esphome/components/http_request/http_request_arduino.h +4 -3
  151. esphome/components/http_request/http_request_host.cpp +141 -0
  152. esphome/components/http_request/http_request_host.h +37 -0
  153. esphome/components/http_request/http_request_idf.cpp +35 -3
  154. esphome/components/http_request/http_request_idf.h +10 -3
  155. esphome/components/http_request/httplib.h +9691 -0
  156. esphome/components/http_request/update/__init__.py +11 -8
  157. esphome/components/i2c/i2c.h +4 -0
  158. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  159. esphome/components/i2s_audio/__init__.py +131 -22
  160. esphome/components/i2s_audio/i2s_audio.h +44 -4
  161. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  162. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  163. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  164. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  165. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  166. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  167. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  168. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  169. esphome/components/image/__init__.py +37 -17
  170. esphome/components/image/image.cpp +25 -8
  171. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  172. esphome/components/key_collector/__init__.py +35 -0
  173. esphome/components/key_collector/key_collector.cpp +8 -0
  174. esphome/components/key_collector/key_collector.h +10 -0
  175. esphome/components/ld2410/ld2410.h +1 -1
  176. esphome/components/ld2450/ld2450.h +1 -1
  177. esphome/components/light/__init__.py +57 -0
  178. esphome/components/lock/__init__.py +51 -4
  179. esphome/components/lock/automation.h +2 -13
  180. esphome/components/logger/__init__.py +21 -0
  181. esphome/components/logger/logger.cpp +125 -95
  182. esphome/components/logger/logger.h +160 -35
  183. esphome/components/logger/task_log_buffer.cpp +138 -0
  184. esphome/components/logger/task_log_buffer.h +69 -0
  185. esphome/components/lvgl/__init__.py +13 -5
  186. esphome/components/lvgl/automation.py +50 -1
  187. esphome/components/lvgl/defines.py +0 -1
  188. esphome/components/lvgl/lv_validation.py +10 -1
  189. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  190. esphome/components/lvgl/schemas.py +14 -14
  191. esphome/components/lvgl/text/__init__.py +1 -2
  192. esphome/components/lvgl/widgets/arc.py +7 -6
  193. esphome/components/lvgl/widgets/buttonmatrix.py +3 -3
  194. esphome/components/lvgl/widgets/checkbox.py +2 -2
  195. esphome/components/lvgl/widgets/dropdown.py +2 -1
  196. esphome/components/lvgl/widgets/img.py +15 -12
  197. esphome/components/mapping/__init__.py +134 -0
  198. esphome/components/max7219digit/max7219digit.cpp +27 -27
  199. esphome/components/mdns/__init__.py +11 -5
  200. esphome/components/mdns/mdns_component.cpp +11 -5
  201. esphome/components/mdns/mdns_component.h +3 -2
  202. esphome/components/mdns/mdns_esp32.cpp +4 -3
  203. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  204. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  205. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  206. esphome/components/media_player/__init__.py +40 -6
  207. esphome/components/micro_wake_word/__init__.py +99 -31
  208. esphome/components/micro_wake_word/automation.h +54 -0
  209. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  210. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  211. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  212. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  213. esphome/components/micro_wake_word/streaming_model.h +85 -13
  214. esphome/components/microphone/__init__.py +139 -9
  215. esphome/components/microphone/automation.h +14 -2
  216. esphome/components/microphone/microphone.cpp +21 -0
  217. esphome/components/microphone/microphone.h +14 -5
  218. esphome/components/microphone/microphone_source.cpp +95 -0
  219. esphome/components/microphone/microphone_source.h +80 -0
  220. esphome/components/mics_4514/sensor.py +25 -14
  221. esphome/components/midea/climate.py +3 -4
  222. esphome/components/midea_ir/climate.py +3 -5
  223. esphome/components/mipi_spi/__init__.py +15 -0
  224. esphome/components/mipi_spi/display.py +474 -0
  225. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  226. esphome/components/mipi_spi/mipi_spi.h +171 -0
  227. esphome/components/mipi_spi/models/__init__.py +65 -0
  228. esphome/components/mipi_spi/models/amoled.py +72 -0
  229. esphome/components/mipi_spi/models/commands.py +82 -0
  230. esphome/components/mipi_spi/models/cyd.py +10 -0
  231. esphome/components/mipi_spi/models/ili.py +749 -0
  232. esphome/components/mipi_spi/models/jc.py +260 -0
  233. esphome/components/mipi_spi/models/lanbon.py +15 -0
  234. esphome/components/mipi_spi/models/lilygo.py +60 -0
  235. esphome/components/mipi_spi/models/waveshare.py +139 -0
  236. esphome/components/mitsubishi/climate.py +2 -5
  237. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  238. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  239. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  240. esphome/components/mlx90393/sensor.py +5 -0
  241. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  242. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  243. esphome/components/mqtt/__init__.py +1 -1
  244. esphome/components/mqtt/mqtt_client.cpp +5 -1
  245. esphome/components/mqtt/mqtt_const.h +4 -0
  246. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  247. esphome/components/mqtt/mqtt_fan.h +2 -0
  248. esphome/components/network/__init__.py +1 -1
  249. esphome/components/nextion/base_component.py +17 -16
  250. esphome/components/nextion/display.py +11 -2
  251. esphome/components/nextion/nextion.cpp +39 -1
  252. esphome/components/nextion/nextion.h +50 -0
  253. esphome/components/noblex/climate.py +2 -9
  254. esphome/components/number/__init__.py +12 -9
  255. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  256. esphome/components/one_wire/one_wire_bus.h +14 -8
  257. esphome/components/online_image/bmp_image.cpp +48 -11
  258. esphome/components/online_image/bmp_image.h +2 -0
  259. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  260. esphome/components/opentherm/number/__init__.py +11 -20
  261. esphome/components/opentherm/sensor/__init__.py +3 -3
  262. esphome/components/opentherm/switch/__init__.py +3 -5
  263. esphome/components/output/lock/__init__.py +11 -9
  264. esphome/components/packages/__init__.py +33 -31
  265. esphome/components/packet_transport/__init__.py +201 -0
  266. esphome/components/packet_transport/binary_sensor.py +19 -0
  267. esphome/components/packet_transport/packet_transport.cpp +534 -0
  268. esphome/components/packet_transport/packet_transport.h +154 -0
  269. esphome/components/packet_transport/sensor.py +19 -0
  270. esphome/components/pca9685/pca9685_output.cpp +2 -1
  271. esphome/components/pid/climate.py +2 -4
  272. esphome/components/pm2005/__init__.py +1 -0
  273. esphome/components/pm2005/pm2005.cpp +123 -0
  274. esphome/components/pm2005/pm2005.h +46 -0
  275. esphome/components/pm2005/sensor.py +86 -0
  276. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  277. esphome/components/pmsa003i/pmsa003i.h +25 -25
  278. esphome/components/pmsx003/pmsx003.cpp +193 -229
  279. esphome/components/pmsx003/pmsx003.h +51 -33
  280. esphome/components/pmsx003/sensor.py +21 -11
  281. esphome/components/pn7150/pn7150.h +2 -2
  282. esphome/components/pn7160/pn7160.h +2 -2
  283. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  284. esphome/components/prometheus/prometheus_handler.h +17 -0
  285. esphome/components/psram/__init__.py +7 -5
  286. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  287. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  288. esphome/components/qspi_dbi/__init__.py +0 -1
  289. esphome/components/qspi_dbi/display.py +2 -1
  290. esphome/components/qspi_dbi/models.py +1 -2
  291. esphome/components/remote_base/__init__.py +91 -0
  292. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  293. esphome/components/remote_base/beo4_protocol.h +43 -0
  294. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  295. esphome/components/remote_base/gobox_protocol.h +54 -0
  296. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  297. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  298. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  299. esphome/components/scd30/sensor.py +2 -3
  300. esphome/components/scd4x/sensor.py +4 -5
  301. esphome/components/sdp3x/sensor.py +2 -1
  302. esphome/components/select/__init__.py +19 -20
  303. esphome/components/sen5x/sensor.py +1 -1
  304. esphome/components/sensor/__init__.py +158 -14
  305. esphome/components/sensor/filter.cpp +23 -0
  306. esphome/components/sensor/filter.h +22 -0
  307. esphome/components/sgp4x/sensor.py +1 -1
  308. esphome/components/sht4x/sht4x.cpp +43 -22
  309. esphome/components/sht4x/sht4x.h +1 -1
  310. esphome/components/sml/text_sensor/__init__.py +4 -6
  311. esphome/components/sound_level/__init__.py +0 -0
  312. esphome/components/sound_level/sensor.py +97 -0
  313. esphome/components/sound_level/sound_level.cpp +194 -0
  314. esphome/components/sound_level/sound_level.h +73 -0
  315. esphome/components/speaker/media_player/__init__.py +4 -8
  316. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  317. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  318. esphome/components/speaker/speaker.h +4 -7
  319. esphome/components/speed/fan/__init__.py +17 -16
  320. esphome/components/spi/spi.h +11 -1
  321. esphome/components/sprinkler/__init__.py +18 -19
  322. esphome/components/switch/__init__.py +32 -42
  323. esphome/components/syslog/__init__.py +41 -0
  324. esphome/components/syslog/esphome_syslog.cpp +49 -0
  325. esphome/components/syslog/esphome_syslog.h +27 -0
  326. esphome/components/tca9555/tca9555.cpp +11 -6
  327. esphome/components/tcl112/climate.py +2 -9
  328. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  329. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  330. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  331. esphome/components/template/cover/__init__.py +27 -21
  332. esphome/components/template/fan/__init__.py +14 -12
  333. esphome/components/template/lock/__init__.py +20 -25
  334. esphome/components/template/lock/automation.h +18 -0
  335. esphome/components/template/text/__init__.py +4 -3
  336. esphome/components/template/valve/__init__.py +32 -21
  337. esphome/components/template/valve/automation.h +24 -0
  338. esphome/components/text/__init__.py +32 -1
  339. esphome/components/text_sensor/__init__.py +24 -29
  340. esphome/components/thermostat/climate.py +5 -5
  341. esphome/components/time_based/cover.py +17 -16
  342. esphome/components/tm1638/switch/__init__.py +10 -7
  343. esphome/components/tormatic/cover.py +4 -5
  344. esphome/components/toshiba/climate.py +3 -5
  345. esphome/components/touchscreen/touchscreen.cpp +3 -1
  346. esphome/components/tt21100/touchscreen/tt21100.cpp +1 -1
  347. esphome/components/tuya/climate/__init__.py +5 -6
  348. esphome/components/tuya/cover/__init__.py +6 -11
  349. esphome/components/tuya/select/__init__.py +15 -5
  350. esphome/components/tuya/select/tuya_select.cpp +6 -1
  351. esphome/components/tuya/select/tuya_select.h +5 -1
  352. esphome/components/uart/packet_transport/__init__.py +20 -0
  353. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  354. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  355. esphome/components/udp/__init__.py +126 -128
  356. esphome/components/udp/automation.h +40 -0
  357. esphome/components/udp/binary_sensor.py +3 -25
  358. esphome/components/udp/packet_transport/__init__.py +29 -0
  359. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  360. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  361. esphome/components/udp/sensor.py +3 -25
  362. esphome/components/udp/udp_component.cpp +26 -470
  363. esphome/components/udp/udp_component.h +21 -128
  364. esphome/components/update/__init__.py +31 -1
  365. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  366. esphome/components/uptime/text_sensor/__init__.py +47 -7
  367. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  368. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  369. esphome/components/valve/__init__.py +34 -3
  370. esphome/components/valve/automation.h +1 -19
  371. esphome/components/vl53l0x/sensor.py +11 -0
  372. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  373. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  374. esphome/components/voice_assistant/__init__.py +36 -10
  375. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  376. esphome/components/voice_assistant/voice_assistant.h +26 -25
  377. esphome/components/waveshare_epaper/display.py +6 -0
  378. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  379. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  380. esphome/components/whirlpool/climate.py +3 -5
  381. esphome/components/whynter/climate.py +3 -5
  382. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  383. esphome/components/yashima/climate.py +6 -6
  384. esphome/components/zhlt01/climate.py +2 -7
  385. esphome/config_validation.py +38 -58
  386. esphome/const.py +15 -1
  387. esphome/core/__init__.py +2 -0
  388. esphome/core/application.cpp +1 -0
  389. esphome/core/application.h +4 -0
  390. esphome/core/automation.h +4 -3
  391. esphome/core/component.cpp +19 -3
  392. esphome/core/component.h +5 -0
  393. esphome/core/defines.h +23 -17
  394. esphome/core/macros.h +4 -0
  395. esphome/core/scheduler.cpp +3 -0
  396. esphome/cpp_generator.py +6 -2
  397. esphome/dashboard/web_server.py +3 -3
  398. esphome/helpers.py +39 -0
  399. esphome/loader.py +4 -0
  400. esphome/mqtt.py +21 -8
  401. esphome/platformio_api.py +1 -1
  402. esphome/schema_extractors.py +0 -1
  403. esphome/vscode.py +15 -0
  404. esphome/wizard.py +2 -2
  405. esphome/zeroconf.py +7 -3
  406. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/METADATA +10 -11
  407. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/RECORD +411 -352
  408. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/WHEEL +1 -1
  409. esphome/components/esp32_ble/const_esp32c6.h +0 -74
  410. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/entry_points.txt +0 -0
  411. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/licenses/LICENSE +0 -0
  412. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/top_level.txt +0 -0
@@ -67,7 +67,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
67
67
 
68
68
  // ========== INTERNAL METHODS ==========
69
69
  // (In most use cases you won't need these)
70
- void send_state_internal(bool state, bool is_initial);
70
+ void send_state_internal(bool state);
71
71
 
72
72
  /// Return whether this binary sensor has outputted a state.
73
73
  virtual bool has_state() const;
@@ -9,37 +9,37 @@ namespace binary_sensor {
9
9
 
10
10
  static const char *const TAG = "sensor.filter";
11
11
 
12
- void Filter::output(bool value, bool is_initial) {
12
+ void Filter::output(bool value) {
13
13
  if (!this->dedup_.next(value))
14
14
  return;
15
15
 
16
16
  if (this->next_ == nullptr) {
17
- this->parent_->send_state_internal(value, is_initial);
17
+ this->parent_->send_state_internal(value);
18
18
  } else {
19
- this->next_->input(value, is_initial);
19
+ this->next_->input(value);
20
20
  }
21
21
  }
22
- void Filter::input(bool value, bool is_initial) {
23
- auto b = this->new_value(value, is_initial);
22
+ void Filter::input(bool value) {
23
+ auto b = this->new_value(value);
24
24
  if (b.has_value()) {
25
- this->output(*b, is_initial);
25
+ this->output(*b);
26
26
  }
27
27
  }
28
28
 
29
- optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
29
+ optional<bool> DelayedOnOffFilter::new_value(bool value) {
30
30
  if (value) {
31
- this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
31
+ this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
32
32
  } else {
33
- this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
33
+ this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
34
34
  }
35
35
  return {};
36
36
  }
37
37
 
38
38
  float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
39
39
 
40
- optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
40
+ optional<bool> DelayedOnFilter::new_value(bool value) {
41
41
  if (value) {
42
- this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
42
+ this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
43
43
  return {};
44
44
  } else {
45
45
  this->cancel_timeout("ON");
@@ -49,9 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
49
49
 
50
50
  float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
51
51
 
52
- optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
52
+ optional<bool> DelayedOffFilter::new_value(bool value) {
53
53
  if (!value) {
54
- this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
54
+ this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
55
55
  return {};
56
56
  } else {
57
57
  this->cancel_timeout("OFF");
@@ -61,11 +61,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
61
61
 
62
62
  float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
63
63
 
64
- optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
64
+ optional<bool> InvertFilter::new_value(bool value) { return !value; }
65
65
 
66
66
  AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
67
67
 
68
- optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
68
+ optional<bool> AutorepeatFilter::new_value(bool value) {
69
69
  if (value) {
70
70
  // Ignore if already running
71
71
  if (this->active_timing_ != 0)
@@ -101,7 +101,7 @@ void AutorepeatFilter::next_timing_() {
101
101
 
102
102
  void AutorepeatFilter::next_value_(bool val) {
103
103
  const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
104
- this->output(val, false); // This is at least the second one so not initial
104
+ this->output(val);
105
105
  this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
106
106
  }
107
107
 
@@ -109,18 +109,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
109
109
 
110
110
  LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
111
111
 
112
- optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
112
+ optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
113
113
 
114
- optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
114
+ optional<bool> SettleFilter::new_value(bool value) {
115
115
  if (!this->steady_) {
116
- this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
116
+ this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
117
117
  this->steady_ = true;
118
- this->output(value, is_initial);
118
+ this->output(value);
119
119
  });
120
120
  return {};
121
121
  } else {
122
122
  this->steady_ = false;
123
- this->output(value, is_initial);
123
+ this->output(value);
124
124
  this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
125
125
  return value;
126
126
  }
@@ -14,11 +14,11 @@ class BinarySensor;
14
14
 
15
15
  class Filter {
16
16
  public:
17
- virtual optional<bool> new_value(bool value, bool is_initial) = 0;
17
+ virtual optional<bool> new_value(bool value) = 0;
18
18
 
19
- void input(bool value, bool is_initial);
19
+ void input(bool value);
20
20
 
21
- void output(bool value, bool is_initial);
21
+ void output(bool value);
22
22
 
23
23
  protected:
24
24
  friend BinarySensor;
@@ -30,7 +30,7 @@ class Filter {
30
30
 
31
31
  class DelayedOnOffFilter : public Filter, public Component {
32
32
  public:
33
- optional<bool> new_value(bool value, bool is_initial) override;
33
+ optional<bool> new_value(bool value) override;
34
34
 
35
35
  float get_setup_priority() const override;
36
36
 
@@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
44
44
 
45
45
  class DelayedOnFilter : public Filter, public Component {
46
46
  public:
47
- optional<bool> new_value(bool value, bool is_initial) override;
47
+ optional<bool> new_value(bool value) override;
48
48
 
49
49
  float get_setup_priority() const override;
50
50
 
@@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
56
56
 
57
57
  class DelayedOffFilter : public Filter, public Component {
58
58
  public:
59
- optional<bool> new_value(bool value, bool is_initial) override;
59
+ optional<bool> new_value(bool value) override;
60
60
 
61
61
  float get_setup_priority() const override;
62
62
 
@@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
68
68
 
69
69
  class InvertFilter : public Filter {
70
70
  public:
71
- optional<bool> new_value(bool value, bool is_initial) override;
71
+ optional<bool> new_value(bool value) override;
72
72
  };
73
73
 
74
74
  struct AutorepeatFilterTiming {
@@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
86
86
  public:
87
87
  explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
88
88
 
89
- optional<bool> new_value(bool value, bool is_initial) override;
89
+ optional<bool> new_value(bool value) override;
90
90
 
91
91
  float get_setup_priority() const override;
92
92
 
@@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
102
102
  public:
103
103
  explicit LambdaFilter(std::function<optional<bool>(bool)> f);
104
104
 
105
- optional<bool> new_value(bool value, bool is_initial) override;
105
+ optional<bool> new_value(bool value) override;
106
106
 
107
107
  protected:
108
108
  std::function<optional<bool>(bool)> f_;
@@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
110
110
 
111
111
  class SettleFilter : public Filter, public Component {
112
112
  public:
113
- optional<bool> new_value(bool value, bool is_initial) override;
113
+ optional<bool> new_value(bool value) override;
114
114
 
115
115
  float get_setup_priority() const override;
116
116
 
@@ -45,7 +45,7 @@ static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
45
45
  static const uint8_t BL0906_V_RMS = 0x16;
46
46
 
47
47
  // Total power
48
- static const uint8_t BL0906_WATT_SUM = 0X2C;
48
+ static const uint8_t BL0906_WATT_SUM = 0x2C;
49
49
 
50
50
  // Current1~6
51
51
  static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
@@ -56,29 +56,29 @@ static const uint8_t BL0906_I_5_RMS = 0x13;
56
56
  static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
57
57
 
58
58
  // Power1~6
59
- static const uint8_t BL0906_WATT_1 = 0X23; // power_1
60
- static const uint8_t BL0906_WATT_2 = 0X24;
61
- static const uint8_t BL0906_WATT_3 = 0X25;
62
- static const uint8_t BL0906_WATT_4 = 0X26;
63
- static const uint8_t BL0906_WATT_5 = 0X29;
64
- static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
59
+ static const uint8_t BL0906_WATT_1 = 0x23; // power_1
60
+ static const uint8_t BL0906_WATT_2 = 0x24;
61
+ static const uint8_t BL0906_WATT_3 = 0x25;
62
+ static const uint8_t BL0906_WATT_4 = 0x26;
63
+ static const uint8_t BL0906_WATT_5 = 0x29;
64
+ static const uint8_t BL0906_WATT_6 = 0x2A; // power_6
65
65
 
66
66
  // Active pulse count, unsigned
67
- static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
68
- static const uint8_t BL0906_CF_2_CNT = 0X31;
69
- static const uint8_t BL0906_CF_3_CNT = 0X32;
70
- static const uint8_t BL0906_CF_4_CNT = 0X33;
71
- static const uint8_t BL0906_CF_5_CNT = 0X36;
72
- static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
67
+ static const uint8_t BL0906_CF_1_CNT = 0x30; // Channel_1
68
+ static const uint8_t BL0906_CF_2_CNT = 0x31;
69
+ static const uint8_t BL0906_CF_3_CNT = 0x32;
70
+ static const uint8_t BL0906_CF_4_CNT = 0x33;
71
+ static const uint8_t BL0906_CF_5_CNT = 0x36;
72
+ static const uint8_t BL0906_CF_6_CNT = 0x37; // Channel_6
73
73
 
74
74
  // Total active pulse count, unsigned
75
- static const uint8_t BL0906_CF_SUM_CNT = 0X39;
75
+ static const uint8_t BL0906_CF_SUM_CNT = 0x39;
76
76
 
77
77
  // Voltage frequency cycle
78
- static const uint8_t BL0906_FREQUENCY = 0X4E;
78
+ static const uint8_t BL0906_FREQUENCY = 0x4E;
79
79
 
80
80
  // Internal temperature
81
- static const uint8_t BL0906_TEMPERATURE = 0X5E;
81
+ static const uint8_t BL0906_TEMPERATURE = 0x5E;
82
82
 
83
83
  // Calibration register
84
84
  // RMS gain adjustment register
@@ -4,7 +4,6 @@ from esphome.components import ble_client, esp32_ble_tracker, text_sensor
4
4
  import esphome.config_validation as cv
5
5
  from esphome.const import (
6
6
  CONF_CHARACTERISTIC_UUID,
7
- CONF_ID,
8
7
  CONF_NOTIFY,
9
8
  CONF_SERVICE_UUID,
10
9
  CONF_TRIGGER_ID,
@@ -32,9 +31,9 @@ BLETextSensorNotifyTrigger = ble_client_ns.class_(
32
31
  )
33
32
 
34
33
  CONFIG_SCHEMA = cv.All(
35
- text_sensor.TEXT_SENSOR_SCHEMA.extend(
34
+ text_sensor.text_sensor_schema(BLETextSensor)
35
+ .extend(
36
36
  {
37
- cv.GenerateID(): cv.declare_id(BLETextSensor),
38
37
  cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
39
38
  cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
40
39
  cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
@@ -54,7 +53,7 @@ CONFIG_SCHEMA = cv.All(
54
53
 
55
54
 
56
55
  async def to_code(config):
57
- var = cg.new_Pvariable(config[CONF_ID])
56
+ var = await text_sensor.new_text_sensor(config)
58
57
  if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
59
58
  cg.add(
60
59
  var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
@@ -101,7 +100,6 @@ async def to_code(config):
101
100
  await cg.register_component(var, config)
102
101
  await ble_client.register_ble_node(var, config)
103
102
  cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
104
- await text_sensor.register_text_sensor(var, config)
105
103
  for conf in config.get(CONF_ON_NOTIFY, []):
106
104
  trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
107
105
  await ble_client.register_ble_node(trigger, config)
@@ -73,9 +73,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
73
73
  resp.address = this->address_;
74
74
  resp.handle = param->read.handle;
75
75
  resp.data.reserve(param->read.value_len);
76
- for (uint16_t i = 0; i < param->read.value_len; i++) {
77
- resp.data.push_back(param->read.value[i]);
78
- }
76
+ // Use bulk insert instead of individual push_backs
77
+ resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len);
79
78
  this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
80
79
  break;
81
80
  }
@@ -127,9 +126,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
127
126
  resp.address = this->address_;
128
127
  resp.handle = param->notify.handle;
129
128
  resp.data.reserve(param->notify.value_len);
130
- for (uint16_t i = 0; i < param->notify.value_len; i++) {
131
- resp.data.push_back(param->notify.value[i]);
132
- }
129
+ // Use bulk insert instead of individual push_backs
130
+ resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len);
133
131
  this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
134
132
  break;
135
133
  }
@@ -25,6 +25,22 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
25
25
 
26
26
  BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
27
27
 
28
+ void BluetoothProxy::setup() {
29
+ this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
30
+ if (this->api_connection_ != nullptr) {
31
+ this->send_bluetooth_scanner_state_(state);
32
+ }
33
+ });
34
+ }
35
+
36
+ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
37
+ api::BluetoothScannerStateResponse resp;
38
+ resp.state = static_cast<api::enums::BluetoothScannerState>(state);
39
+ resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
40
+ : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
41
+ this->api_connection_->send_bluetooth_scanner_state_response(resp);
42
+ }
43
+
28
44
  bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
29
45
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
30
46
  return false;
@@ -35,33 +51,60 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
35
51
  return true;
36
52
  }
37
53
 
54
+ static constexpr size_t FLUSH_BATCH_SIZE = 8;
55
+ static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
56
+ static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
57
+ return batch_buffer;
58
+ }
59
+
38
60
  bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
39
61
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
40
62
  return false;
41
63
 
42
- api::BluetoothLERawAdvertisementsResponse resp;
64
+ // Get the batch buffer reference
65
+ auto &batch_buffer = get_batch_buffer();
66
+
67
+ // Reserve additional capacity if needed
68
+ size_t new_size = batch_buffer.size() + count;
69
+ if (batch_buffer.capacity() < new_size) {
70
+ batch_buffer.reserve(new_size);
71
+ }
72
+
73
+ // Add new advertisements to the batch buffer
43
74
  for (size_t i = 0; i < count; i++) {
44
75
  auto &result = advertisements[i];
45
- api::BluetoothLERawAdvertisement adv;
76
+ uint8_t length = result.adv_data_len + result.scan_rsp_len;
77
+
78
+ batch_buffer.emplace_back();
79
+ auto &adv = batch_buffer.back();
46
80
  adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
47
81
  adv.rssi = result.rssi;
48
82
  adv.address_type = result.ble_addr_type;
83
+ adv.data.assign(&result.ble_adv[0], &result.ble_adv[length]);
49
84
 
50
- uint8_t length = result.adv_data_len + result.scan_rsp_len;
51
- adv.data.reserve(length);
52
- for (uint16_t i = 0; i < length; i++) {
53
- adv.data.push_back(result.ble_adv[i]);
54
- }
55
-
56
- resp.advertisements.push_back(std::move(adv));
57
-
58
- ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
85
+ ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
59
86
  result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
60
87
  }
61
- ESP_LOGV(TAG, "Proxying %d packets", count);
62
- this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
88
+
89
+ // Only send if we've accumulated a good batch size to maximize batching efficiency
90
+ // https://github.com/esphome/backlog/issues/21
91
+ if (batch_buffer.size() >= FLUSH_BATCH_SIZE) {
92
+ this->flush_pending_advertisements();
93
+ }
94
+
63
95
  return true;
64
96
  }
97
+
98
+ void BluetoothProxy::flush_pending_advertisements() {
99
+ auto &batch_buffer = get_batch_buffer();
100
+ if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
101
+ return;
102
+
103
+ api::BluetoothLERawAdvertisementsResponse resp;
104
+ resp.advertisements.swap(batch_buffer);
105
+ this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
106
+ }
107
+
65
108
  void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
66
109
  api::BluetoothLEAdvertisementResponse resp;
67
110
  resp.address = device.address_uint64();
@@ -69,21 +112,34 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
69
112
  if (!device.get_name().empty())
70
113
  resp.name = device.get_name();
71
114
  resp.rssi = device.get_rssi();
72
- for (auto uuid : device.get_service_uuids()) {
73
- resp.service_uuids.push_back(uuid.to_string());
115
+
116
+ // Pre-allocate vectors based on known sizes
117
+ auto service_uuids = device.get_service_uuids();
118
+ resp.service_uuids.reserve(service_uuids.size());
119
+ for (auto &uuid : service_uuids) {
120
+ resp.service_uuids.emplace_back(uuid.to_string());
74
121
  }
75
- for (auto &data : device.get_service_datas()) {
76
- api::BluetoothServiceData service_data;
122
+
123
+ // Pre-allocate service data vector
124
+ auto service_datas = device.get_service_datas();
125
+ resp.service_data.reserve(service_datas.size());
126
+ for (auto &data : service_datas) {
127
+ resp.service_data.emplace_back();
128
+ auto &service_data = resp.service_data.back();
77
129
  service_data.uuid = data.uuid.to_string();
78
130
  service_data.data.assign(data.data.begin(), data.data.end());
79
- resp.service_data.push_back(std::move(service_data));
80
131
  }
81
- for (auto &data : device.get_manufacturer_datas()) {
82
- api::BluetoothServiceData manufacturer_data;
132
+
133
+ // Pre-allocate manufacturer data vector
134
+ auto manufacturer_datas = device.get_manufacturer_datas();
135
+ resp.manufacturer_data.reserve(manufacturer_datas.size());
136
+ for (auto &data : manufacturer_datas) {
137
+ resp.manufacturer_data.emplace_back();
138
+ auto &manufacturer_data = resp.manufacturer_data.back();
83
139
  manufacturer_data.uuid = data.uuid.to_string();
84
140
  manufacturer_data.data.assign(data.data.begin(), data.data.end());
85
- resp.manufacturer_data.push_back(std::move(manufacturer_data));
86
141
  }
142
+
87
143
  this->api_connection_->send_bluetooth_le_advertisement(resp);
88
144
  }
89
145
 
@@ -117,6 +173,18 @@ void BluetoothProxy::loop() {
117
173
  }
118
174
  return;
119
175
  }
176
+
177
+ // Flush any pending BLE advertisements that have been accumulated but not yet sent
178
+ if (this->raw_advertisements_) {
179
+ static uint32_t last_flush_time = 0;
180
+ uint32_t now = millis();
181
+
182
+ // Flush accumulated advertisements every 100ms
183
+ if (now - last_flush_time >= 100) {
184
+ this->flush_pending_advertisements();
185
+ last_flush_time = now;
186
+ }
187
+ }
120
188
  for (auto *connection : this->connections_) {
121
189
  if (connection->send_service_ == connection->service_count_) {
122
190
  connection->send_service_ = DONE_SENDING_SERVICES;
@@ -145,11 +213,27 @@ void BluetoothProxy::loop() {
145
213
  }
146
214
  api::BluetoothGATTGetServicesResponse resp;
147
215
  resp.address = connection->get_address();
216
+ resp.services.reserve(1); // Always one service per response in this implementation
148
217
  api::BluetoothGATTService service_resp;
149
218
  service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
150
219
  service_resp.handle = service_result.start_handle;
151
220
  uint16_t char_offset = 0;
152
221
  esp_gattc_char_elem_t char_result;
222
+ // Get the number of characteristics directly with one call
223
+ uint16_t total_char_count = 0;
224
+ esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
225
+ connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
226
+ service_result.start_handle, service_result.end_handle, 0, &total_char_count);
227
+
228
+ if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
229
+ // Only reserve if we successfully got a count
230
+ service_resp.characteristics.reserve(total_char_count);
231
+ } else if (char_count_status != ESP_GATT_OK) {
232
+ ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
233
+ connection->address_str().c_str(), char_count_status);
234
+ }
235
+
236
+ // Now process characteristics
153
237
  while (true) { // characteristics
154
238
  uint16_t char_count = 1;
155
239
  esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
@@ -171,6 +255,23 @@ void BluetoothProxy::loop() {
171
255
  characteristic_resp.handle = char_result.char_handle;
172
256
  characteristic_resp.properties = char_result.properties;
173
257
  char_offset++;
258
+
259
+ // Get the number of descriptors directly with one call
260
+ uint16_t total_desc_count = 0;
261
+ esp_gatt_status_t desc_count_status =
262
+ esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
263
+ char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
264
+
265
+ if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
266
+ // Only reserve if we successfully got a count
267
+ characteristic_resp.descriptors.reserve(total_desc_count);
268
+ } else if (desc_count_status != ESP_GATT_OK) {
269
+ ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
270
+ connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
271
+ desc_count_status);
272
+ }
273
+
274
+ // Now process descriptors
174
275
  uint16_t desc_offset = 0;
175
276
  esp_gattc_descr_elem_t desc_result;
176
277
  while (true) { // descriptors
@@ -453,6 +554,8 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
453
554
  this->api_connection_ = api_connection;
454
555
  this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
455
556
  this->parent_->recalculate_advertisement_parser_types();
557
+
558
+ this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
456
559
  }
457
560
 
458
561
  void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@@ -525,6 +628,17 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
525
628
  this->api_connection_->send_bluetooth_device_unpairing_response(call);
526
629
  }
527
630
 
631
+ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
632
+ if (this->parent_->get_scan_active() == active) {
633
+ return;
634
+ }
635
+ ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
636
+ this->parent_->set_scan_active(active);
637
+ this->parent_->stop_scan();
638
+ this->parent_->set_scan_continuous(
639
+ true); // Set this to true to automatically start scanning again when it has cleaned up.
640
+ }
641
+
528
642
  BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
529
643
 
530
644
  } // namespace bluetooth_proxy
@@ -41,6 +41,7 @@ enum BluetoothProxyFeature : uint32_t {
41
41
  FEATURE_PAIRING = 1 << 3,
42
42
  FEATURE_CACHE_CLEARING = 1 << 4,
43
43
  FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
44
+ FEATURE_STATE_AND_MODE = 1 << 6,
44
45
  };
45
46
 
46
47
  enum BluetoothProxySubscriptionFlag : uint32_t {
@@ -53,7 +54,9 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
53
54
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
54
55
  bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
55
56
  void dump_config() override;
57
+ void setup() override;
56
58
  void loop() override;
59
+ void flush_pending_advertisements();
57
60
  esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
58
61
 
59
62
  void register_connection(BluetoothConnection *connection) {
@@ -84,6 +87,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
84
87
  void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
85
88
  void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
86
89
 
90
+ void bluetooth_scanner_set_mode(bool active);
91
+
87
92
  static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
88
93
  bd_addr[0] = (address >> 40) & 0xff;
89
94
  bd_addr[1] = (address >> 32) & 0xff;
@@ -107,6 +112,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
107
112
  uint32_t flags = 0;
108
113
  flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
109
114
  flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
115
+ flags |= BluetoothProxyFeature::FEATURE_STATE_AND_MODE;
110
116
  if (this->active_) {
111
117
  flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
112
118
  flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
@@ -124,6 +130,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
124
130
 
125
131
  protected:
126
132
  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
133
+ void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
127
134
 
128
135
  BluetoothConnection *get_connection_(uint64_t address, bool reserve);
129
136
 
@@ -44,7 +44,7 @@ ButtonPressTrigger = button_ns.class_(
44
44
  validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
45
45
 
46
46
 
47
- BUTTON_SCHEMA = (
47
+ _BUTTON_SCHEMA = (
48
48
  cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
49
49
  .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
50
50
  .extend(
@@ -60,15 +60,13 @@ BUTTON_SCHEMA = (
60
60
  )
61
61
  )
62
62
 
63
- _UNDEF = object()
64
-
65
63
 
66
64
  def button_schema(
67
65
  class_: MockObjClass,
68
66
  *,
69
- icon: str = _UNDEF,
70
- entity_category: str = _UNDEF,
71
- device_class: str = _UNDEF,
67
+ icon: str = cv.UNDEFINED,
68
+ entity_category: str = cv.UNDEFINED,
69
+ device_class: str = cv.UNDEFINED,
72
70
  ) -> cv.Schema:
73
71
  schema = {cv.GenerateID(): cv.declare_id(class_)}
74
72
 
@@ -77,10 +75,15 @@ def button_schema(
77
75
  (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
78
76
  (CONF_DEVICE_CLASS, device_class, validate_device_class),
79
77
  ]:
80
- if default is not _UNDEF:
78
+ if default is not cv.UNDEFINED:
81
79
  schema[cv.Optional(key, default=default)] = validator
82
80
 
83
- return BUTTON_SCHEMA.extend(schema)
81
+ return _BUTTON_SCHEMA.extend(schema)
82
+
83
+
84
+ # Remove before 2025.11.0
85
+ BUTTON_SCHEMA = button_schema(Button)
86
+ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
84
87
 
85
88
 
86
89
  async def setup_button_core_(var, config):
@@ -86,6 +86,9 @@ void Canbus::loop() {
86
86
  data.push_back(can_message.data[i]);
87
87
  }
88
88
 
89
+ this->callback_manager_(can_message.can_id, can_message.use_extended_id, can_message.remote_transmission_request,
90
+ data);
91
+
89
92
  // fire all triggers
90
93
  for (auto *trigger : this->triggers_) {
91
94
  if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
@@ -81,6 +81,20 @@ class Canbus : public Component {
81
81
  void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
82
82
 
83
83
  void add_trigger(CanbusTrigger *trigger);
84
+ /**
85
+ * Add a callback to be called when a CAN message is received. All received messages
86
+ * are passed to the callback without filtering.
87
+ *
88
+ * The callback function receives:
89
+ * - can_id of the received data
90
+ * - extended_id True if the can_id is an extended id
91
+ * - rtr If this is a remote transmission request
92
+ * - data The message data
93
+ */
94
+ void add_callback(
95
+ std::function<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)> callback) {
96
+ this->callback_manager_.add(std::move(callback));
97
+ }
84
98
 
85
99
  protected:
86
100
  template<typename... Ts> friend class CanbusSendAction;
@@ -88,6 +102,8 @@ class Canbus : public Component {
88
102
  uint32_t can_id_;
89
103
  bool use_extended_id_;
90
104
  CanSpeed bit_rate_;
105
+ CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
106
+ callback_manager_{};
91
107
 
92
108
  virtual bool setup_internal();
93
109
  virtual Error send_message(struct CanFrame *frame);