esphome 2025.4.2__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 (444) 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/as7341/as7341.h +1 -1
  26. esphome/components/at581x/at581x.h +4 -4
  27. esphome/components/atm90e32/__init__.py +1 -0
  28. esphome/components/atm90e32/atm90e32.cpp +576 -199
  29. esphome/components/atm90e32/atm90e32.h +128 -31
  30. esphome/components/atm90e32/atm90e32_reg.h +4 -2
  31. esphome/components/atm90e32/button/__init__.py +62 -10
  32. esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
  33. esphome/components/atm90e32/button/atm90e32_button.h +36 -4
  34. esphome/components/atm90e32/number/__init__.py +130 -0
  35. esphome/components/atm90e32/number/atm90e32_number.h +16 -0
  36. esphome/components/atm90e32/sensor.py +21 -4
  37. esphome/components/atm90e32/text_sensor/__init__.py +48 -0
  38. esphome/components/audio/__init__.py +96 -49
  39. esphome/components/audio/audio.h +48 -0
  40. esphome/components/audio/audio_decoder.cpp +1 -1
  41. esphome/components/audio/audio_resampler.cpp +2 -0
  42. esphome/components/audio/audio_resampler.h +1 -0
  43. esphome/components/ballu/climate.py +2 -9
  44. esphome/components/bang_bang/climate.py +5 -6
  45. esphome/components/bedjet/bedjet_hub.cpp +1 -0
  46. esphome/components/bedjet/climate/__init__.py +3 -8
  47. esphome/components/bedjet/fan/__init__.py +2 -11
  48. esphome/components/binary/fan/__init__.py +13 -16
  49. esphome/components/binary_sensor/__init__.py +13 -10
  50. esphome/components/bl0906/constants.h +16 -16
  51. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  52. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  53. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
  54. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  55. esphome/components/button/__init__.py +11 -8
  56. esphome/components/canbus/canbus.cpp +3 -0
  57. esphome/components/canbus/canbus.h +16 -0
  58. esphome/components/ccs811/sensor.py +9 -6
  59. esphome/components/climate/__init__.py +35 -2
  60. esphome/components/climate/climate_mode.h +1 -1
  61. esphome/components/climate/climate_traits.h +63 -57
  62. esphome/components/climate_ir/__init__.py +57 -17
  63. esphome/components/climate_ir_lg/climate.py +2 -5
  64. esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
  65. esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
  66. esphome/components/color/__init__.py +2 -0
  67. esphome/components/const/__init__.py +5 -0
  68. esphome/components/coolix/climate.py +2 -9
  69. esphome/components/copy/cover/__init__.py +10 -9
  70. esphome/components/copy/fan/__init__.py +11 -9
  71. esphome/components/copy/lock/__init__.py +11 -9
  72. esphome/components/copy/text/__init__.py +9 -6
  73. esphome/components/cover/__init__.py +37 -2
  74. esphome/components/cse7766/cse7766.cpp +2 -1
  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/current_based/current_based_cover.cpp +2 -1
  82. esphome/components/daikin/climate.py +2 -9
  83. esphome/components/daikin/daikin.cpp +15 -9
  84. esphome/components/daikin/daikin.h +5 -5
  85. esphome/components/daikin_arc/climate.py +2 -7
  86. esphome/components/daikin_brc/climate.py +3 -5
  87. esphome/components/dallas_temp/dallas_temp.cpp +17 -24
  88. esphome/components/dallas_temp/dallas_temp.h +0 -1
  89. esphome/components/daly_bms/daly_bms.cpp +2 -1
  90. esphome/components/debug/debug_component.cpp +6 -1
  91. esphome/components/debug/debug_component.h +8 -0
  92. esphome/components/debug/debug_esp32.cpp +109 -254
  93. esphome/components/debug/sensor.py +14 -0
  94. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  95. esphome/components/delonghi/climate.py +2 -9
  96. esphome/components/demo/__init__.py +18 -20
  97. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  98. esphome/components/dps310/sensor.py +6 -6
  99. esphome/components/ee895/sensor.py +9 -9
  100. esphome/components/emmeti/climate.py +2 -9
  101. esphome/components/endstop/cover.py +17 -16
  102. esphome/components/endstop/endstop_cover.cpp +2 -1
  103. esphome/components/ens160_base/__init__.py +12 -9
  104. esphome/components/esp32/__init__.py +60 -3
  105. esphome/components/esp32/core.cpp +11 -5
  106. esphome/components/esp32/gpio.cpp +86 -24
  107. esphome/components/esp32/gpio.py +15 -16
  108. esphome/components/esp32/gpio_esp32.py +1 -2
  109. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  110. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  111. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  112. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  113. esphome/components/esp32_ble/ble.cpp +1 -0
  114. esphome/components/esp32_ble/ble.h +5 -3
  115. esphome/components/esp32_ble/ble_advertising.cpp +2 -1
  116. esphome/components/esp32_ble/ble_advertising.h +1 -0
  117. esphome/components/esp32_ble_server/__init__.py +3 -0
  118. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  119. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  120. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  121. esphome/components/esp32_camera/__init__.py +1 -1
  122. esphome/components/esp32_camera/esp32_camera.cpp +2 -10
  123. esphome/components/esp32_camera/esp32_camera.h +1 -1
  124. esphome/components/esp32_can/esp32_can.cpp +1 -1
  125. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  126. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  127. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  128. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  129. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  130. esphome/components/esp8266/gpio.cpp +69 -8
  131. esphome/components/ethernet/ethernet_component.cpp +1 -1
  132. esphome/components/event/__init__.py +13 -10
  133. esphome/components/factory_reset/switch/__init__.py +7 -21
  134. esphome/components/fan/__init__.py +52 -5
  135. esphome/components/fastled_base/__init__.py +1 -4
  136. esphome/components/fastled_base/fastled_light.cpp +1 -1
  137. esphome/components/feedback/cover.py +38 -33
  138. esphome/components/feedback/feedback_cover.cpp +2 -1
  139. esphome/components/fujitsu_general/climate.py +2 -9
  140. esphome/components/gcja5/gcja5.cpp +2 -1
  141. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  142. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  143. esphome/components/gpio_expander/cached_gpio.h +22 -7
  144. esphome/components/gps/__init__.py +47 -17
  145. esphome/components/gps/gps.cpp +42 -23
  146. esphome/components/gps/gps.h +17 -13
  147. esphome/components/graph/__init__.py +1 -2
  148. esphome/components/gree/climate.py +4 -6
  149. esphome/components/gree/gree.cpp +16 -2
  150. esphome/components/gree/gree.h +2 -2
  151. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  152. esphome/components/haier/climate.py +37 -34
  153. esphome/components/hbridge/fan/__init__.py +19 -17
  154. esphome/components/he60r/cover.py +4 -5
  155. esphome/components/heatpumpir/climate.py +3 -6
  156. esphome/components/hitachi_ac344/climate.py +2 -9
  157. esphome/components/hitachi_ac424/climate.py +2 -9
  158. esphome/components/hm3301/hm3301.h +1 -1
  159. esphome/components/hte501/sensor.py +6 -6
  160. esphome/components/http_request/__init__.py +39 -6
  161. esphome/components/http_request/http_request.cpp +20 -0
  162. esphome/components/http_request/http_request.h +57 -15
  163. esphome/components/http_request/http_request_arduino.cpp +22 -6
  164. esphome/components/http_request/http_request_arduino.h +4 -3
  165. esphome/components/http_request/http_request_host.cpp +141 -0
  166. esphome/components/http_request/http_request_host.h +37 -0
  167. esphome/components/http_request/http_request_idf.cpp +35 -3
  168. esphome/components/http_request/http_request_idf.h +10 -3
  169. esphome/components/http_request/httplib.h +9691 -0
  170. esphome/components/http_request/update/__init__.py +11 -8
  171. esphome/components/hyt271/sensor.py +6 -6
  172. esphome/components/i2c/i2c.h +4 -0
  173. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  174. esphome/components/i2s_audio/__init__.py +131 -22
  175. esphome/components/i2s_audio/i2s_audio.h +44 -4
  176. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  177. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  178. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  179. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  180. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  181. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  182. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  183. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  184. esphome/components/image/__init__.py +37 -17
  185. esphome/components/image/image.cpp +25 -8
  186. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  187. esphome/components/key_collector/__init__.py +35 -0
  188. esphome/components/key_collector/key_collector.cpp +8 -0
  189. esphome/components/key_collector/key_collector.h +10 -0
  190. esphome/components/kuntze/kuntze.cpp +2 -1
  191. esphome/components/ld2410/ld2410.h +1 -1
  192. esphome/components/ld2450/ld2450.h +1 -1
  193. esphome/components/light/__init__.py +57 -0
  194. esphome/components/lock/__init__.py +51 -4
  195. esphome/components/lock/automation.h +2 -13
  196. esphome/components/logger/__init__.py +22 -0
  197. esphome/components/logger/logger.cpp +154 -103
  198. esphome/components/logger/logger.h +211 -36
  199. esphome/components/logger/task_log_buffer.cpp +138 -0
  200. esphome/components/logger/task_log_buffer.h +69 -0
  201. esphome/components/lvgl/__init__.py +13 -5
  202. esphome/components/lvgl/automation.py +50 -1
  203. esphome/components/lvgl/defines.py +0 -1
  204. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  205. esphome/components/lvgl/text/__init__.py +1 -2
  206. esphome/components/mapping/__init__.py +134 -0
  207. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  208. esphome/components/max7219digit/max7219digit.cpp +28 -27
  209. esphome/components/mdns/__init__.py +11 -5
  210. esphome/components/mdns/mdns_component.cpp +11 -5
  211. esphome/components/mdns/mdns_component.h +3 -2
  212. esphome/components/mdns/mdns_esp32.cpp +4 -3
  213. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  214. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  215. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  216. esphome/components/media_player/__init__.py +33 -1
  217. esphome/components/mhz19/sensor.py +11 -7
  218. esphome/components/micro_wake_word/__init__.py +99 -31
  219. esphome/components/micro_wake_word/automation.h +54 -0
  220. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  221. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  222. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  223. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  224. esphome/components/micro_wake_word/streaming_model.h +85 -13
  225. esphome/components/microphone/__init__.py +139 -9
  226. esphome/components/microphone/automation.h +14 -2
  227. esphome/components/microphone/microphone.cpp +21 -0
  228. esphome/components/microphone/microphone.h +14 -5
  229. esphome/components/microphone/microphone_source.cpp +95 -0
  230. esphome/components/microphone/microphone_source.h +80 -0
  231. esphome/components/mics_4514/sensor.py +25 -14
  232. esphome/components/midea/climate.py +3 -4
  233. esphome/components/midea_ir/climate.py +3 -5
  234. esphome/components/mipi_spi/__init__.py +15 -0
  235. esphome/components/mipi_spi/display.py +474 -0
  236. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  237. esphome/components/mipi_spi/mipi_spi.h +171 -0
  238. esphome/components/mipi_spi/models/__init__.py +65 -0
  239. esphome/components/mipi_spi/models/amoled.py +72 -0
  240. esphome/components/mipi_spi/models/commands.py +82 -0
  241. esphome/components/mipi_spi/models/cyd.py +10 -0
  242. esphome/components/mipi_spi/models/ili.py +749 -0
  243. esphome/components/mipi_spi/models/jc.py +260 -0
  244. esphome/components/mipi_spi/models/lanbon.py +15 -0
  245. esphome/components/mipi_spi/models/lilygo.py +60 -0
  246. esphome/components/mipi_spi/models/waveshare.py +139 -0
  247. esphome/components/mitsubishi/climate.py +2 -5
  248. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  249. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  250. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  251. esphome/components/mlx90393/sensor.py +5 -0
  252. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  253. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  254. esphome/components/modbus/modbus.cpp +2 -1
  255. esphome/components/mqtt/__init__.py +1 -1
  256. esphome/components/mqtt/mqtt_client.cpp +6 -2
  257. esphome/components/mqtt/mqtt_const.h +4 -0
  258. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  259. esphome/components/mqtt/mqtt_fan.h +2 -0
  260. esphome/components/ms5611/sensor.py +6 -6
  261. esphome/components/ms8607/sensor.py +3 -3
  262. esphome/components/network/__init__.py +1 -1
  263. esphome/components/nextion/base_component.py +17 -16
  264. esphome/components/nextion/display.py +11 -2
  265. esphome/components/nextion/nextion.cpp +39 -1
  266. esphome/components/nextion/nextion.h +50 -0
  267. esphome/components/noblex/climate.py +2 -9
  268. esphome/components/number/__init__.py +12 -9
  269. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  270. esphome/components/one_wire/one_wire_bus.h +14 -8
  271. esphome/components/online_image/bmp_image.cpp +48 -11
  272. esphome/components/online_image/bmp_image.h +2 -0
  273. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  274. esphome/components/opentherm/number/__init__.py +11 -20
  275. esphome/components/opentherm/sensor/__init__.py +3 -3
  276. esphome/components/opentherm/switch/__init__.py +3 -5
  277. esphome/components/output/lock/__init__.py +11 -9
  278. esphome/components/packages/__init__.py +33 -31
  279. esphome/components/packet_transport/__init__.py +201 -0
  280. esphome/components/packet_transport/binary_sensor.py +19 -0
  281. esphome/components/packet_transport/packet_transport.cpp +534 -0
  282. esphome/components/packet_transport/packet_transport.h +154 -0
  283. esphome/components/packet_transport/sensor.py +19 -0
  284. esphome/components/pca9685/pca9685_output.cpp +2 -1
  285. esphome/components/pid/climate.py +2 -4
  286. esphome/components/pm2005/__init__.py +1 -0
  287. esphome/components/pm2005/pm2005.cpp +123 -0
  288. esphome/components/pm2005/pm2005.h +46 -0
  289. esphome/components/pm2005/sensor.py +86 -0
  290. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  291. esphome/components/pmsa003i/pmsa003i.h +25 -25
  292. esphome/components/pmsx003/pmsx003.cpp +195 -230
  293. esphome/components/pmsx003/pmsx003.h +51 -33
  294. esphome/components/pmsx003/sensor.py +21 -11
  295. esphome/components/pn7150/pn7150.h +2 -2
  296. esphome/components/pn7160/pn7160.h +2 -2
  297. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  298. esphome/components/prometheus/prometheus_handler.h +17 -0
  299. esphome/components/psram/__init__.py +7 -5
  300. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  301. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  302. esphome/components/pzem004t/pzem004t.cpp +2 -1
  303. esphome/components/qspi_dbi/__init__.py +0 -1
  304. esphome/components/qspi_dbi/display.py +2 -1
  305. esphome/components/qspi_dbi/models.py +1 -2
  306. esphome/components/remote_base/__init__.py +91 -0
  307. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  308. esphome/components/remote_base/beo4_protocol.h +43 -0
  309. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  310. esphome/components/remote_base/gobox_protocol.h +54 -0
  311. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  312. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  313. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  314. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  315. esphome/components/scd30/sensor.py +2 -3
  316. esphome/components/scd4x/sensor.py +4 -5
  317. esphome/components/sdp3x/sensor.py +2 -1
  318. esphome/components/sds011/sds011.cpp +2 -1
  319. esphome/components/select/__init__.py +19 -20
  320. esphome/components/sen5x/sen5x.cpp +55 -36
  321. esphome/components/sen5x/sensor.py +1 -1
  322. esphome/components/senseair/sensor.py +3 -3
  323. esphome/components/sensor/__init__.py +158 -14
  324. esphome/components/sensor/filter.cpp +23 -0
  325. esphome/components/sensor/filter.h +22 -0
  326. esphome/components/sgp30/sensor.py +14 -16
  327. esphome/components/sgp4x/sensor.py +1 -1
  328. esphome/components/sht4x/sht4x.cpp +43 -22
  329. esphome/components/sht4x/sht4x.h +1 -1
  330. esphome/components/shtcx/sensor.py +6 -6
  331. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  332. esphome/components/sml/text_sensor/__init__.py +4 -6
  333. esphome/components/sound_level/__init__.py +0 -0
  334. esphome/components/sound_level/sensor.py +97 -0
  335. esphome/components/sound_level/sound_level.cpp +194 -0
  336. esphome/components/sound_level/sound_level.h +73 -0
  337. esphome/components/speaker/media_player/__init__.py +4 -8
  338. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  339. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  340. esphome/components/speaker/speaker.h +4 -7
  341. esphome/components/speed/fan/__init__.py +17 -16
  342. esphome/components/spi/spi.h +11 -1
  343. esphome/components/sprinkler/__init__.py +18 -19
  344. esphome/components/sprinkler/sprinkler.cpp +6 -5
  345. esphome/components/switch/__init__.py +32 -42
  346. esphome/components/syslog/__init__.py +41 -0
  347. esphome/components/syslog/esphome_syslog.cpp +49 -0
  348. esphome/components/syslog/esphome_syslog.h +27 -0
  349. esphome/components/t6615/sensor.py +3 -3
  350. esphome/components/t6615/t6615.cpp +2 -1
  351. esphome/components/tca9555/tca9555.cpp +11 -6
  352. esphome/components/tcl112/climate.py +2 -9
  353. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  354. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  355. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  356. esphome/components/template/cover/__init__.py +27 -21
  357. esphome/components/template/fan/__init__.py +14 -12
  358. esphome/components/template/lock/__init__.py +20 -25
  359. esphome/components/template/lock/automation.h +18 -0
  360. esphome/components/template/text/__init__.py +4 -3
  361. esphome/components/template/valve/__init__.py +32 -21
  362. esphome/components/template/valve/automation.h +24 -0
  363. esphome/components/text/__init__.py +32 -1
  364. esphome/components/text_sensor/__init__.py +24 -29
  365. esphome/components/thermostat/climate.py +5 -5
  366. esphome/components/time_based/cover.py +17 -16
  367. esphome/components/time_based/time_based_cover.cpp +2 -1
  368. esphome/components/tm1638/switch/__init__.py +10 -7
  369. esphome/components/tormatic/cover.py +4 -5
  370. esphome/components/toshiba/climate.py +3 -5
  371. esphome/components/touchscreen/touchscreen.cpp +3 -1
  372. esphome/components/tuya/climate/__init__.py +5 -6
  373. esphome/components/tuya/cover/__init__.py +6 -11
  374. esphome/components/tuya/select/__init__.py +15 -5
  375. esphome/components/tuya/select/tuya_select.cpp +6 -1
  376. esphome/components/tuya/select/tuya_select.h +5 -1
  377. esphome/components/uart/packet_transport/__init__.py +20 -0
  378. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  379. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  380. esphome/components/uart/switch/uart_switch.cpp +2 -1
  381. esphome/components/udp/__init__.py +126 -128
  382. esphome/components/udp/automation.h +40 -0
  383. esphome/components/udp/binary_sensor.py +3 -25
  384. esphome/components/udp/packet_transport/__init__.py +29 -0
  385. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  386. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  387. esphome/components/udp/sensor.py +3 -25
  388. esphome/components/udp/udp_component.cpp +26 -470
  389. esphome/components/udp/udp_component.h +21 -128
  390. esphome/components/update/__init__.py +31 -1
  391. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  392. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  393. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  394. esphome/components/uptime/text_sensor/__init__.py +47 -7
  395. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  396. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  397. esphome/components/valve/__init__.py +34 -3
  398. esphome/components/valve/automation.h +1 -19
  399. esphome/components/vl53l0x/sensor.py +11 -0
  400. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  401. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  402. esphome/components/voice_assistant/__init__.py +36 -10
  403. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  404. esphome/components/voice_assistant/voice_assistant.h +26 -25
  405. esphome/components/waveshare_epaper/display.py +6 -0
  406. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  407. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  408. esphome/components/weikai/weikai.cpp +0 -52
  409. esphome/components/whirlpool/climate.py +3 -5
  410. esphome/components/whynter/climate.py +3 -5
  411. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  412. esphome/components/yashima/climate.py +6 -6
  413. esphome/components/zhlt01/climate.py +2 -7
  414. esphome/config.py +13 -13
  415. esphome/config_validation.py +38 -58
  416. esphome/const.py +15 -1
  417. esphome/core/__init__.py +2 -0
  418. esphome/core/application.cpp +27 -10
  419. esphome/core/application.h +9 -1
  420. esphome/core/automation.h +4 -3
  421. esphome/core/component.cpp +28 -7
  422. esphome/core/component.h +10 -1
  423. esphome/core/defines.h +23 -17
  424. esphome/core/doxygen.h +13 -0
  425. esphome/core/macros.h +4 -0
  426. esphome/core/scheduler.cpp +7 -1
  427. esphome/cpp_generator.py +6 -2
  428. esphome/dashboard/web_server.py +3 -3
  429. esphome/helpers.py +39 -0
  430. esphome/loader.py +4 -0
  431. esphome/log.py +15 -19
  432. esphome/mqtt.py +23 -10
  433. esphome/platformio_api.py +1 -1
  434. esphome/schema_extractors.py +0 -1
  435. esphome/voluptuous_schema.py +3 -1
  436. esphome/vscode.py +15 -0
  437. esphome/wizard.py +47 -37
  438. esphome/zeroconf.py +7 -3
  439. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/METADATA +10 -11
  440. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/RECORD +444 -383
  441. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/WHEEL +1 -1
  442. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/entry_points.txt +0 -0
  443. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/licenses/LICENSE +0 -0
  444. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/top_level.txt +0 -0
@@ -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
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "esphome/core/log.h"
4
4
  #include "esphome/core/macros.h"
5
+ #include "esphome/core/application.h"
5
6
 
6
7
  #ifdef USE_ESP32
7
8
 
@@ -25,6 +26,22 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
25
26
 
26
27
  BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
27
28
 
29
+ void BluetoothProxy::setup() {
30
+ this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
31
+ if (this->api_connection_ != nullptr) {
32
+ this->send_bluetooth_scanner_state_(state);
33
+ }
34
+ });
35
+ }
36
+
37
+ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
38
+ api::BluetoothScannerStateResponse resp;
39
+ resp.state = static_cast<api::enums::BluetoothScannerState>(state);
40
+ resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
41
+ : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
42
+ this->api_connection_->send_bluetooth_scanner_state_response(resp);
43
+ }
44
+
28
45
  bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
29
46
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
30
47
  return false;
@@ -35,33 +52,60 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
35
52
  return true;
36
53
  }
37
54
 
55
+ static constexpr size_t FLUSH_BATCH_SIZE = 8;
56
+ static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
57
+ static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
58
+ return batch_buffer;
59
+ }
60
+
38
61
  bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
39
62
  if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
40
63
  return false;
41
64
 
42
- api::BluetoothLERawAdvertisementsResponse resp;
65
+ // Get the batch buffer reference
66
+ auto &batch_buffer = get_batch_buffer();
67
+
68
+ // Reserve additional capacity if needed
69
+ size_t new_size = batch_buffer.size() + count;
70
+ if (batch_buffer.capacity() < new_size) {
71
+ batch_buffer.reserve(new_size);
72
+ }
73
+
74
+ // Add new advertisements to the batch buffer
43
75
  for (size_t i = 0; i < count; i++) {
44
76
  auto &result = advertisements[i];
45
- api::BluetoothLERawAdvertisement adv;
77
+ uint8_t length = result.adv_data_len + result.scan_rsp_len;
78
+
79
+ batch_buffer.emplace_back();
80
+ auto &adv = batch_buffer.back();
46
81
  adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
47
82
  adv.rssi = result.rssi;
48
83
  adv.address_type = result.ble_addr_type;
84
+ adv.data.assign(&result.ble_adv[0], &result.ble_adv[length]);
49
85
 
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],
86
+ ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
59
87
  result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
60
88
  }
61
- ESP_LOGV(TAG, "Proxying %d packets", count);
62
- this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
89
+
90
+ // Only send if we've accumulated a good batch size to maximize batching efficiency
91
+ // https://github.com/esphome/backlog/issues/21
92
+ if (batch_buffer.size() >= FLUSH_BATCH_SIZE) {
93
+ this->flush_pending_advertisements();
94
+ }
95
+
63
96
  return true;
64
97
  }
98
+
99
+ void BluetoothProxy::flush_pending_advertisements() {
100
+ auto &batch_buffer = get_batch_buffer();
101
+ if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
102
+ return;
103
+
104
+ api::BluetoothLERawAdvertisementsResponse resp;
105
+ resp.advertisements.swap(batch_buffer);
106
+ this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
107
+ }
108
+
65
109
  void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
66
110
  api::BluetoothLEAdvertisementResponse resp;
67
111
  resp.address = device.address_uint64();
@@ -69,21 +113,34 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
69
113
  if (!device.get_name().empty())
70
114
  resp.name = device.get_name();
71
115
  resp.rssi = device.get_rssi();
72
- for (auto uuid : device.get_service_uuids()) {
73
- resp.service_uuids.push_back(uuid.to_string());
116
+
117
+ // Pre-allocate vectors based on known sizes
118
+ auto service_uuids = device.get_service_uuids();
119
+ resp.service_uuids.reserve(service_uuids.size());
120
+ for (auto &uuid : service_uuids) {
121
+ resp.service_uuids.emplace_back(uuid.to_string());
74
122
  }
75
- for (auto &data : device.get_service_datas()) {
76
- api::BluetoothServiceData service_data;
123
+
124
+ // Pre-allocate service data vector
125
+ auto service_datas = device.get_service_datas();
126
+ resp.service_data.reserve(service_datas.size());
127
+ for (auto &data : service_datas) {
128
+ resp.service_data.emplace_back();
129
+ auto &service_data = resp.service_data.back();
77
130
  service_data.uuid = data.uuid.to_string();
78
131
  service_data.data.assign(data.data.begin(), data.data.end());
79
- resp.service_data.push_back(std::move(service_data));
80
132
  }
81
- for (auto &data : device.get_manufacturer_datas()) {
82
- api::BluetoothServiceData manufacturer_data;
133
+
134
+ // Pre-allocate manufacturer data vector
135
+ auto manufacturer_datas = device.get_manufacturer_datas();
136
+ resp.manufacturer_data.reserve(manufacturer_datas.size());
137
+ for (auto &data : manufacturer_datas) {
138
+ resp.manufacturer_data.emplace_back();
139
+ auto &manufacturer_data = resp.manufacturer_data.back();
83
140
  manufacturer_data.uuid = data.uuid.to_string();
84
141
  manufacturer_data.data.assign(data.data.begin(), data.data.end());
85
- resp.manufacturer_data.push_back(std::move(manufacturer_data));
86
142
  }
143
+
87
144
  this->api_connection_->send_bluetooth_le_advertisement(resp);
88
145
  }
89
146
 
@@ -117,6 +174,18 @@ void BluetoothProxy::loop() {
117
174
  }
118
175
  return;
119
176
  }
177
+
178
+ // Flush any pending BLE advertisements that have been accumulated but not yet sent
179
+ if (this->raw_advertisements_) {
180
+ static uint32_t last_flush_time = 0;
181
+ uint32_t now = App.get_loop_component_start_time();
182
+
183
+ // Flush accumulated advertisements every 100ms
184
+ if (now - last_flush_time >= 100) {
185
+ this->flush_pending_advertisements();
186
+ last_flush_time = now;
187
+ }
188
+ }
120
189
  for (auto *connection : this->connections_) {
121
190
  if (connection->send_service_ == connection->service_count_) {
122
191
  connection->send_service_ = DONE_SENDING_SERVICES;
@@ -145,11 +214,27 @@ void BluetoothProxy::loop() {
145
214
  }
146
215
  api::BluetoothGATTGetServicesResponse resp;
147
216
  resp.address = connection->get_address();
217
+ resp.services.reserve(1); // Always one service per response in this implementation
148
218
  api::BluetoothGATTService service_resp;
149
219
  service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
150
220
  service_resp.handle = service_result.start_handle;
151
221
  uint16_t char_offset = 0;
152
222
  esp_gattc_char_elem_t char_result;
223
+ // Get the number of characteristics directly with one call
224
+ uint16_t total_char_count = 0;
225
+ esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
226
+ connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
227
+ service_result.start_handle, service_result.end_handle, 0, &total_char_count);
228
+
229
+ if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
230
+ // Only reserve if we successfully got a count
231
+ service_resp.characteristics.reserve(total_char_count);
232
+ } else if (char_count_status != ESP_GATT_OK) {
233
+ ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
234
+ connection->address_str().c_str(), char_count_status);
235
+ }
236
+
237
+ // Now process characteristics
153
238
  while (true) { // characteristics
154
239
  uint16_t char_count = 1;
155
240
  esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
@@ -171,6 +256,23 @@ void BluetoothProxy::loop() {
171
256
  characteristic_resp.handle = char_result.char_handle;
172
257
  characteristic_resp.properties = char_result.properties;
173
258
  char_offset++;
259
+
260
+ // Get the number of descriptors directly with one call
261
+ uint16_t total_desc_count = 0;
262
+ esp_gatt_status_t desc_count_status =
263
+ esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
264
+ char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
265
+
266
+ if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
267
+ // Only reserve if we successfully got a count
268
+ characteristic_resp.descriptors.reserve(total_desc_count);
269
+ } else if (desc_count_status != ESP_GATT_OK) {
270
+ ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
271
+ connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
272
+ desc_count_status);
273
+ }
274
+
275
+ // Now process descriptors
174
276
  uint16_t desc_offset = 0;
175
277
  esp_gattc_descr_elem_t desc_result;
176
278
  while (true) { // descriptors
@@ -453,6 +555,8 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
453
555
  this->api_connection_ = api_connection;
454
556
  this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
455
557
  this->parent_->recalculate_advertisement_parser_types();
558
+
559
+ this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
456
560
  }
457
561
 
458
562
  void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@@ -525,6 +629,17 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
525
629
  this->api_connection_->send_bluetooth_device_unpairing_response(call);
526
630
  }
527
631
 
632
+ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
633
+ if (this->parent_->get_scan_active() == active) {
634
+ return;
635
+ }
636
+ ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
637
+ this->parent_->set_scan_active(active);
638
+ this->parent_->stop_scan();
639
+ this->parent_->set_scan_continuous(
640
+ true); // Set this to true to automatically start scanning again when it has cleaned up.
641
+ }
642
+
528
643
  BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
529
644
 
530
645
  } // 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);
@@ -32,14 +32,14 @@ CONFIG_SCHEMA = (
32
32
  cv.Schema(
33
33
  {
34
34
  cv.GenerateID(): cv.declare_id(CCS811Component),
35
- cv.Required(CONF_ECO2): sensor.sensor_schema(
35
+ cv.Optional(CONF_ECO2): sensor.sensor_schema(
36
36
  unit_of_measurement=UNIT_PARTS_PER_MILLION,
37
37
  icon=ICON_MOLECULE_CO2,
38
38
  accuracy_decimals=0,
39
39
  device_class=DEVICE_CLASS_CARBON_DIOXIDE,
40
40
  state_class=STATE_CLASS_MEASUREMENT,
41
41
  ),
42
- cv.Required(CONF_TVOC): sensor.sensor_schema(
42
+ cv.Optional(CONF_TVOC): sensor.sensor_schema(
43
43
  unit_of_measurement=UNIT_PARTS_PER_BILLION,
44
44
  icon=ICON_RADIATOR,
45
45
  accuracy_decimals=0,
@@ -64,10 +64,13 @@ async def to_code(config):
64
64
  await cg.register_component(var, config)
65
65
  await i2c.register_i2c_device(var, config)
66
66
 
67
- sens = await sensor.new_sensor(config[CONF_ECO2])
68
- cg.add(var.set_co2(sens))
69
- sens = await sensor.new_sensor(config[CONF_TVOC])
70
- cg.add(var.set_tvoc(sens))
67
+ if eco2_config := config.get(CONF_ECO2):
68
+ sens = await sensor.new_sensor(eco2_config)
69
+ cg.add(var.set_co2(sens))
70
+
71
+ if tvoc_config := config.get(CONF_TVOC):
72
+ sens = await sensor.new_sensor(tvoc_config)
73
+ cg.add(var.set_tvoc(sens))
71
74
 
72
75
  if version_config := config.get(CONF_VERSION):
73
76
  sens = await text_sensor.new_text_sensor(version_config)
@@ -11,9 +11,11 @@ from esphome.const import (
11
11
  CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
12
12
  CONF_CUSTOM_FAN_MODE,
13
13
  CONF_CUSTOM_PRESET,
14
+ CONF_ENTITY_CATEGORY,
14
15
  CONF_FAN_MODE,
15
16
  CONF_FAN_MODE_COMMAND_TOPIC,
16
17
  CONF_FAN_MODE_STATE_TOPIC,
18
+ CONF_ICON,
17
19
  CONF_ID,
18
20
  CONF_MAX_TEMPERATURE,
19
21
  CONF_MIN_TEMPERATURE,
@@ -46,6 +48,7 @@ from esphome.const import (
46
48
  CONF_WEB_SERVER,
47
49
  )
48
50
  from esphome.core import CORE, coroutine_with_priority
51
+ from esphome.cpp_generator import MockObjClass
49
52
  from esphome.cpp_helpers import setup_entity
50
53
 
51
54
  IS_PLATFORM_COMPONENT = True
@@ -151,12 +154,11 @@ ControlTrigger = climate_ns.class_(
151
154
  "ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref"))
152
155
  )
153
156
 
154
- CLIMATE_SCHEMA = (
157
+ _CLIMATE_SCHEMA = (
155
158
  cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
156
159
  .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
157
160
  .extend(
158
161
  {
159
- cv.GenerateID(): cv.declare_id(Climate),
160
162
  cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
161
163
  cv.Optional(CONF_VISUAL, default={}): cv.Schema(
162
164
  {
@@ -245,6 +247,31 @@ CLIMATE_SCHEMA = (
245
247
  )
246
248
 
247
249
 
250
+ def climate_schema(
251
+ class_: MockObjClass,
252
+ *,
253
+ entity_category: str = cv.UNDEFINED,
254
+ icon: str = cv.UNDEFINED,
255
+ ) -> cv.Schema:
256
+ schema = {
257
+ cv.GenerateID(): cv.declare_id(class_),
258
+ }
259
+
260
+ for key, default, validator in [
261
+ (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
262
+ (CONF_ICON, icon, cv.icon),
263
+ ]:
264
+ if default is not cv.UNDEFINED:
265
+ schema[cv.Optional(key, default=default)] = validator
266
+
267
+ return _CLIMATE_SCHEMA.extend(schema)
268
+
269
+
270
+ # Remove before 2025.11.0
271
+ CLIMATE_SCHEMA = climate_schema(Climate)
272
+ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
273
+
274
+
248
275
  async def setup_climate_core_(var, config):
249
276
  await setup_entity(var, config)
250
277
 
@@ -419,6 +446,12 @@ async def register_climate(var, config):
419
446
  await setup_climate_core_(var, config)
420
447
 
421
448
 
449
+ async def new_climate(config, *args):
450
+ var = cg.new_Pvariable(config[CONF_ID], *args)
451
+ await register_climate(var, config)
452
+ return var
453
+
454
+
422
455
  CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
423
456
  {
424
457
  cv.Required(CONF_ID): cv.use_id(Climate),
@@ -20,7 +20,7 @@ enum ClimateMode : uint8_t {
20
20
  CLIMATE_MODE_FAN_ONLY = 4,
21
21
  /// The climate device is set to dry/humidity mode
22
22
  CLIMATE_MODE_DRY = 5,
23
- /** The climate device is adjusting the temperatre dynamically.
23
+ /** The climate device is adjusting the temperature dynamically.
24
24
  * For example, the target temperature can be adjusted based on a schedule, or learned behavior.
25
25
  * The target temperature can't be adjusted when in this mode.
26
26
  */