esphome 2025.4.2__py3-none-any.whl → 2025.5.0b3__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 (440) 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 +81 -13
  13. esphome/components/api/api_connection.h +13 -1
  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/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/bedjet_hub.cpp +1 -0
  45. esphome/components/bedjet/climate/__init__.py +3 -8
  46. esphome/components/bedjet/fan/__init__.py +2 -11
  47. esphome/components/binary/fan/__init__.py +13 -16
  48. esphome/components/binary_sensor/__init__.py +13 -10
  49. esphome/components/bl0906/constants.h +16 -16
  50. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  51. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  52. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
  53. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  54. esphome/components/button/__init__.py +11 -8
  55. esphome/components/canbus/canbus.cpp +3 -0
  56. esphome/components/canbus/canbus.h +16 -0
  57. esphome/components/ccs811/sensor.py +9 -6
  58. esphome/components/climate/__init__.py +35 -2
  59. esphome/components/climate/climate_mode.h +1 -1
  60. esphome/components/climate/climate_traits.h +63 -57
  61. esphome/components/climate_ir/__init__.py +57 -17
  62. esphome/components/climate_ir_lg/climate.py +2 -5
  63. esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
  64. esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
  65. esphome/components/color/__init__.py +2 -0
  66. esphome/components/const/__init__.py +5 -0
  67. esphome/components/coolix/climate.py +2 -9
  68. esphome/components/copy/cover/__init__.py +10 -9
  69. esphome/components/copy/fan/__init__.py +11 -9
  70. esphome/components/copy/lock/__init__.py +11 -9
  71. esphome/components/copy/text/__init__.py +9 -6
  72. esphome/components/cover/__init__.py +37 -2
  73. esphome/components/cse7766/cse7766.cpp +2 -1
  74. esphome/components/cst226/binary_sensor/__init__.py +28 -0
  75. esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
  76. esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
  77. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
  78. esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
  79. esphome/components/current_based/cover.py +37 -36
  80. esphome/components/current_based/current_based_cover.cpp +2 -1
  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/daly_bms/daly_bms.cpp +2 -1
  89. esphome/components/debug/debug_component.cpp +6 -1
  90. esphome/components/debug/debug_component.h +6 -0
  91. esphome/components/debug/debug_esp32.cpp +109 -254
  92. esphome/components/debug/sensor.py +14 -0
  93. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  94. esphome/components/delonghi/climate.py +2 -9
  95. esphome/components/demo/__init__.py +18 -20
  96. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  97. esphome/components/dps310/sensor.py +6 -6
  98. esphome/components/ee895/sensor.py +9 -9
  99. esphome/components/emmeti/climate.py +2 -9
  100. esphome/components/endstop/cover.py +17 -16
  101. esphome/components/endstop/endstop_cover.cpp +2 -1
  102. esphome/components/ens160_base/__init__.py +12 -9
  103. esphome/components/esp32/__init__.py +60 -3
  104. esphome/components/esp32/core.cpp +11 -5
  105. esphome/components/esp32/gpio.cpp +86 -24
  106. esphome/components/esp32/gpio.py +15 -16
  107. esphome/components/esp32/gpio_esp32.py +1 -2
  108. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  109. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  110. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  111. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  112. esphome/components/esp32_ble/ble.cpp +1 -0
  113. esphome/components/esp32_ble/ble.h +5 -3
  114. esphome/components/esp32_ble/ble_advertising.cpp +2 -1
  115. esphome/components/esp32_ble/ble_advertising.h +1 -0
  116. esphome/components/esp32_ble_server/__init__.py +3 -0
  117. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  118. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  119. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  120. esphome/components/esp32_camera/esp32_camera.cpp +2 -1
  121. esphome/components/esp32_camera/esp32_camera.h +1 -1
  122. esphome/components/esp32_can/esp32_can.cpp +1 -1
  123. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  124. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  125. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  126. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  127. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  128. esphome/components/esp8266/gpio.cpp +69 -8
  129. esphome/components/ethernet/ethernet_component.cpp +1 -1
  130. esphome/components/event/__init__.py +13 -10
  131. esphome/components/factory_reset/switch/__init__.py +7 -21
  132. esphome/components/fan/__init__.py +52 -5
  133. esphome/components/fastled_base/__init__.py +1 -4
  134. esphome/components/fastled_base/fastled_light.cpp +1 -1
  135. esphome/components/feedback/cover.py +38 -33
  136. esphome/components/feedback/feedback_cover.cpp +2 -1
  137. esphome/components/fujitsu_general/climate.py +2 -9
  138. esphome/components/gcja5/gcja5.cpp +2 -1
  139. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  140. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  141. esphome/components/gpio_expander/cached_gpio.h +22 -7
  142. esphome/components/gps/__init__.py +47 -17
  143. esphome/components/gps/gps.cpp +42 -23
  144. esphome/components/gps/gps.h +17 -13
  145. esphome/components/graph/__init__.py +1 -2
  146. esphome/components/gree/climate.py +4 -6
  147. esphome/components/gree/gree.cpp +16 -2
  148. esphome/components/gree/gree.h +2 -2
  149. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  150. esphome/components/haier/climate.py +37 -34
  151. esphome/components/hbridge/fan/__init__.py +19 -17
  152. esphome/components/he60r/cover.py +4 -5
  153. esphome/components/heatpumpir/climate.py +3 -6
  154. esphome/components/hitachi_ac344/climate.py +2 -9
  155. esphome/components/hitachi_ac424/climate.py +2 -9
  156. esphome/components/hm3301/hm3301.h +1 -1
  157. esphome/components/hte501/sensor.py +6 -6
  158. esphome/components/http_request/__init__.py +39 -6
  159. esphome/components/http_request/http_request.cpp +20 -0
  160. esphome/components/http_request/http_request.h +57 -15
  161. esphome/components/http_request/http_request_arduino.cpp +22 -6
  162. esphome/components/http_request/http_request_arduino.h +4 -3
  163. esphome/components/http_request/http_request_host.cpp +141 -0
  164. esphome/components/http_request/http_request_host.h +37 -0
  165. esphome/components/http_request/http_request_idf.cpp +35 -3
  166. esphome/components/http_request/http_request_idf.h +10 -3
  167. esphome/components/http_request/httplib.h +9691 -0
  168. esphome/components/http_request/update/__init__.py +11 -8
  169. esphome/components/hyt271/sensor.py +6 -6
  170. esphome/components/i2c/i2c.h +4 -0
  171. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  172. esphome/components/i2s_audio/__init__.py +131 -22
  173. esphome/components/i2s_audio/i2s_audio.h +44 -4
  174. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  175. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  176. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  177. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  178. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  179. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  180. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  181. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  182. esphome/components/image/__init__.py +37 -17
  183. esphome/components/image/image.cpp +25 -8
  184. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  185. esphome/components/key_collector/__init__.py +35 -0
  186. esphome/components/key_collector/key_collector.cpp +8 -0
  187. esphome/components/key_collector/key_collector.h +10 -0
  188. esphome/components/kuntze/kuntze.cpp +2 -1
  189. esphome/components/ld2410/ld2410.h +1 -1
  190. esphome/components/ld2450/ld2450.h +1 -1
  191. esphome/components/light/__init__.py +57 -0
  192. esphome/components/lock/__init__.py +51 -4
  193. esphome/components/lock/automation.h +2 -13
  194. esphome/components/logger/__init__.py +22 -0
  195. esphome/components/logger/logger.cpp +154 -103
  196. esphome/components/logger/logger.h +211 -36
  197. esphome/components/logger/task_log_buffer.cpp +138 -0
  198. esphome/components/logger/task_log_buffer.h +69 -0
  199. esphome/components/lvgl/__init__.py +13 -5
  200. esphome/components/lvgl/automation.py +50 -1
  201. esphome/components/lvgl/defines.py +0 -1
  202. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  203. esphome/components/lvgl/text/__init__.py +1 -2
  204. esphome/components/mapping/__init__.py +134 -0
  205. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  206. esphome/components/max7219digit/max7219digit.cpp +28 -27
  207. esphome/components/mdns/__init__.py +11 -5
  208. esphome/components/mdns/mdns_component.cpp +11 -5
  209. esphome/components/mdns/mdns_component.h +3 -2
  210. esphome/components/mdns/mdns_esp32.cpp +4 -3
  211. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  212. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  213. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  214. esphome/components/media_player/__init__.py +33 -1
  215. esphome/components/mhz19/sensor.py +11 -7
  216. esphome/components/micro_wake_word/__init__.py +99 -31
  217. esphome/components/micro_wake_word/automation.h +54 -0
  218. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  219. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  220. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  221. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  222. esphome/components/micro_wake_word/streaming_model.h +85 -13
  223. esphome/components/microphone/__init__.py +139 -9
  224. esphome/components/microphone/automation.h +14 -2
  225. esphome/components/microphone/microphone.cpp +21 -0
  226. esphome/components/microphone/microphone.h +14 -5
  227. esphome/components/microphone/microphone_source.cpp +95 -0
  228. esphome/components/microphone/microphone_source.h +80 -0
  229. esphome/components/mics_4514/sensor.py +25 -14
  230. esphome/components/midea/climate.py +3 -4
  231. esphome/components/midea_ir/climate.py +3 -5
  232. esphome/components/mipi_spi/__init__.py +15 -0
  233. esphome/components/mipi_spi/display.py +474 -0
  234. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  235. esphome/components/mipi_spi/mipi_spi.h +171 -0
  236. esphome/components/mipi_spi/models/__init__.py +65 -0
  237. esphome/components/mipi_spi/models/amoled.py +72 -0
  238. esphome/components/mipi_spi/models/commands.py +82 -0
  239. esphome/components/mipi_spi/models/cyd.py +10 -0
  240. esphome/components/mipi_spi/models/ili.py +749 -0
  241. esphome/components/mipi_spi/models/jc.py +260 -0
  242. esphome/components/mipi_spi/models/lanbon.py +15 -0
  243. esphome/components/mipi_spi/models/lilygo.py +60 -0
  244. esphome/components/mipi_spi/models/waveshare.py +139 -0
  245. esphome/components/mitsubishi/climate.py +2 -5
  246. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  247. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  248. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  249. esphome/components/mlx90393/sensor.py +5 -0
  250. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  251. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  252. esphome/components/modbus/modbus.cpp +2 -1
  253. esphome/components/mqtt/__init__.py +1 -1
  254. esphome/components/mqtt/mqtt_client.cpp +6 -2
  255. esphome/components/mqtt/mqtt_const.h +4 -0
  256. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  257. esphome/components/mqtt/mqtt_fan.h +2 -0
  258. esphome/components/ms5611/sensor.py +6 -6
  259. esphome/components/ms8607/sensor.py +3 -3
  260. esphome/components/network/__init__.py +1 -1
  261. esphome/components/nextion/base_component.py +17 -16
  262. esphome/components/nextion/display.py +11 -2
  263. esphome/components/nextion/nextion.cpp +39 -1
  264. esphome/components/nextion/nextion.h +50 -0
  265. esphome/components/noblex/climate.py +2 -9
  266. esphome/components/number/__init__.py +12 -9
  267. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  268. esphome/components/one_wire/one_wire_bus.h +14 -8
  269. esphome/components/online_image/bmp_image.cpp +48 -11
  270. esphome/components/online_image/bmp_image.h +2 -0
  271. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  272. esphome/components/opentherm/number/__init__.py +11 -20
  273. esphome/components/opentherm/sensor/__init__.py +3 -3
  274. esphome/components/opentherm/switch/__init__.py +3 -5
  275. esphome/components/output/lock/__init__.py +11 -9
  276. esphome/components/packages/__init__.py +33 -31
  277. esphome/components/packet_transport/__init__.py +201 -0
  278. esphome/components/packet_transport/binary_sensor.py +19 -0
  279. esphome/components/packet_transport/packet_transport.cpp +534 -0
  280. esphome/components/packet_transport/packet_transport.h +154 -0
  281. esphome/components/packet_transport/sensor.py +19 -0
  282. esphome/components/pca9685/pca9685_output.cpp +2 -1
  283. esphome/components/pid/climate.py +2 -4
  284. esphome/components/pm2005/__init__.py +1 -0
  285. esphome/components/pm2005/pm2005.cpp +123 -0
  286. esphome/components/pm2005/pm2005.h +46 -0
  287. esphome/components/pm2005/sensor.py +86 -0
  288. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  289. esphome/components/pmsa003i/pmsa003i.h +25 -25
  290. esphome/components/pmsx003/pmsx003.cpp +195 -230
  291. esphome/components/pmsx003/pmsx003.h +51 -33
  292. esphome/components/pmsx003/sensor.py +21 -11
  293. esphome/components/pn7150/pn7150.h +2 -2
  294. esphome/components/pn7160/pn7160.h +2 -2
  295. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  296. esphome/components/prometheus/prometheus_handler.h +17 -0
  297. esphome/components/psram/__init__.py +7 -5
  298. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  299. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  300. esphome/components/pzem004t/pzem004t.cpp +2 -1
  301. esphome/components/qspi_dbi/__init__.py +0 -1
  302. esphome/components/qspi_dbi/display.py +2 -1
  303. esphome/components/qspi_dbi/models.py +1 -2
  304. esphome/components/remote_base/__init__.py +91 -0
  305. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  306. esphome/components/remote_base/beo4_protocol.h +43 -0
  307. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  308. esphome/components/remote_base/gobox_protocol.h +54 -0
  309. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  310. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  311. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  312. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  313. esphome/components/scd30/sensor.py +2 -3
  314. esphome/components/scd4x/sensor.py +4 -5
  315. esphome/components/sdp3x/sensor.py +2 -1
  316. esphome/components/sds011/sds011.cpp +2 -1
  317. esphome/components/select/__init__.py +19 -20
  318. esphome/components/sen5x/sen5x.cpp +55 -36
  319. esphome/components/sen5x/sensor.py +1 -1
  320. esphome/components/senseair/sensor.py +3 -3
  321. esphome/components/sensor/__init__.py +158 -14
  322. esphome/components/sensor/filter.cpp +23 -0
  323. esphome/components/sensor/filter.h +22 -0
  324. esphome/components/sgp30/sensor.py +14 -16
  325. esphome/components/sgp4x/sensor.py +1 -1
  326. esphome/components/sht4x/sht4x.cpp +43 -22
  327. esphome/components/sht4x/sht4x.h +1 -1
  328. esphome/components/shtcx/sensor.py +6 -6
  329. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  330. esphome/components/sml/text_sensor/__init__.py +4 -6
  331. esphome/components/sound_level/__init__.py +0 -0
  332. esphome/components/sound_level/sensor.py +97 -0
  333. esphome/components/sound_level/sound_level.cpp +194 -0
  334. esphome/components/sound_level/sound_level.h +73 -0
  335. esphome/components/speaker/media_player/__init__.py +4 -8
  336. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  337. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  338. esphome/components/speaker/speaker.h +4 -7
  339. esphome/components/speed/fan/__init__.py +17 -16
  340. esphome/components/spi/spi.h +11 -1
  341. esphome/components/sprinkler/__init__.py +18 -19
  342. esphome/components/sprinkler/sprinkler.cpp +6 -5
  343. esphome/components/switch/__init__.py +32 -42
  344. esphome/components/syslog/__init__.py +41 -0
  345. esphome/components/syslog/esphome_syslog.cpp +49 -0
  346. esphome/components/syslog/esphome_syslog.h +27 -0
  347. esphome/components/t6615/sensor.py +3 -3
  348. esphome/components/t6615/t6615.cpp +2 -1
  349. esphome/components/tca9555/tca9555.cpp +11 -6
  350. esphome/components/tcl112/climate.py +2 -9
  351. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  352. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  353. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  354. esphome/components/template/cover/__init__.py +27 -21
  355. esphome/components/template/fan/__init__.py +14 -12
  356. esphome/components/template/lock/__init__.py +20 -25
  357. esphome/components/template/lock/automation.h +18 -0
  358. esphome/components/template/text/__init__.py +4 -3
  359. esphome/components/template/valve/__init__.py +32 -21
  360. esphome/components/template/valve/automation.h +24 -0
  361. esphome/components/text/__init__.py +32 -1
  362. esphome/components/text_sensor/__init__.py +24 -29
  363. esphome/components/thermostat/climate.py +5 -5
  364. esphome/components/time_based/cover.py +17 -16
  365. esphome/components/time_based/time_based_cover.cpp +2 -1
  366. esphome/components/tm1638/switch/__init__.py +10 -7
  367. esphome/components/tormatic/cover.py +4 -5
  368. esphome/components/toshiba/climate.py +3 -5
  369. esphome/components/touchscreen/touchscreen.cpp +3 -1
  370. esphome/components/tuya/climate/__init__.py +5 -6
  371. esphome/components/tuya/cover/__init__.py +6 -11
  372. esphome/components/tuya/select/__init__.py +15 -5
  373. esphome/components/tuya/select/tuya_select.cpp +6 -1
  374. esphome/components/tuya/select/tuya_select.h +5 -1
  375. esphome/components/uart/packet_transport/__init__.py +20 -0
  376. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  377. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  378. esphome/components/uart/switch/uart_switch.cpp +2 -1
  379. esphome/components/udp/__init__.py +126 -128
  380. esphome/components/udp/automation.h +40 -0
  381. esphome/components/udp/binary_sensor.py +3 -25
  382. esphome/components/udp/packet_transport/__init__.py +29 -0
  383. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  384. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  385. esphome/components/udp/sensor.py +3 -25
  386. esphome/components/udp/udp_component.cpp +26 -470
  387. esphome/components/udp/udp_component.h +21 -128
  388. esphome/components/update/__init__.py +31 -1
  389. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  390. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  391. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  392. esphome/components/uptime/text_sensor/__init__.py +47 -7
  393. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  394. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  395. esphome/components/valve/__init__.py +34 -3
  396. esphome/components/valve/automation.h +1 -19
  397. esphome/components/vl53l0x/sensor.py +11 -0
  398. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  399. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  400. esphome/components/voice_assistant/__init__.py +36 -10
  401. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  402. esphome/components/voice_assistant/voice_assistant.h +26 -25
  403. esphome/components/waveshare_epaper/display.py +6 -0
  404. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  405. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  406. esphome/components/whirlpool/climate.py +3 -5
  407. esphome/components/whynter/climate.py +3 -5
  408. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  409. esphome/components/yashima/climate.py +6 -6
  410. esphome/components/zhlt01/climate.py +2 -7
  411. esphome/config.py +13 -13
  412. esphome/config_validation.py +38 -58
  413. esphome/const.py +15 -1
  414. esphome/core/__init__.py +2 -0
  415. esphome/core/application.cpp +23 -10
  416. esphome/core/application.h +9 -1
  417. esphome/core/automation.h +4 -3
  418. esphome/core/component.cpp +28 -7
  419. esphome/core/component.h +10 -1
  420. esphome/core/defines.h +23 -17
  421. esphome/core/macros.h +4 -0
  422. esphome/core/scheduler.cpp +7 -1
  423. esphome/cpp_generator.py +6 -2
  424. esphome/dashboard/web_server.py +3 -3
  425. esphome/helpers.py +39 -0
  426. esphome/loader.py +4 -0
  427. esphome/log.py +15 -19
  428. esphome/mqtt.py +23 -10
  429. esphome/platformio_api.py +1 -1
  430. esphome/schema_extractors.py +0 -1
  431. esphome/voluptuous_schema.py +3 -1
  432. esphome/vscode.py +15 -0
  433. esphome/wizard.py +47 -37
  434. esphome/zeroconf.py +7 -3
  435. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/METADATA +10 -11
  436. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/RECORD +440 -380
  437. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/WHEEL +1 -1
  438. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/entry_points.txt +0 -0
  439. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/licenses/LICENSE +0 -0
  440. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,8 @@
4
4
 
5
5
  #include "preprocessor_settings.h"
6
6
 
7
+ #include "esphome/core/preferences.h"
8
+
7
9
  #include <tensorflow/lite/core/c/common.h>
8
10
  #include <tensorflow/lite/micro/micro_interpreter.h>
9
11
  #include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
@@ -11,31 +13,71 @@
11
13
  namespace esphome {
12
14
  namespace micro_wake_word {
13
15
 
16
+ static const uint8_t MIN_SLICES_BEFORE_DETECTION = 100;
14
17
  static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024;
15
18
 
19
+ struct DetectionEvent {
20
+ std::string *wake_word;
21
+ bool detected;
22
+ bool partially_detection; // Set if the most recent probability exceed the threshold, but the sliding window average
23
+ // hasn't yet
24
+ uint8_t max_probability;
25
+ uint8_t average_probability;
26
+ bool blocked_by_vad = false;
27
+ };
28
+
16
29
  class StreamingModel {
17
30
  public:
18
31
  virtual void log_model_config() = 0;
19
- virtual bool determine_detected() = 0;
32
+ virtual DetectionEvent determine_detected() = 0;
20
33
 
34
+ // Performs inference on the given features.
35
+ // - If the model is enabled but not loaded, it will load it
36
+ // - If the model is disabled but loaded, it will unload it
37
+ // Returns true if sucessful or false if there is an error
21
38
  bool perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]);
22
39
 
23
- /// @brief Sets all recent_streaming_probabilities to 0
40
+ /// @brief Sets all recent_streaming_probabilities to 0 and resets the ignore window count
24
41
  void reset_probabilities();
25
42
 
26
- /// @brief Allocates tensor and variable arenas and sets up the model interpreter
27
- /// @param op_resolver MicroMutableOpResolver object that must exist until the model is unloaded
28
- /// @return True if successful, false otherwise
29
- bool load_model(tflite::MicroMutableOpResolver<20> &op_resolver);
30
-
31
43
  /// @brief Destroys the TFLite interpreter and frees the tensor and variable arenas' memory
32
44
  void unload_model();
33
45
 
46
+ /// @brief Enable the model. The next performing_streaming_inference call will load it.
47
+ virtual void enable() { this->enabled_ = true; }
48
+
49
+ /// @brief Disable the model. The next performing_streaming_inference call will unload it.
50
+ virtual void disable() { this->enabled_ = false; }
51
+
52
+ /// @brief Return true if the model is enabled.
53
+ bool is_enabled() const { return this->enabled_; }
54
+
55
+ bool get_unprocessed_probability_status() const { return this->unprocessed_probability_status_; }
56
+
57
+ // Quantized probability cutoffs mapping 0.0 - 1.0 to 0 - 255
58
+ uint8_t get_default_probability_cutoff() const { return this->default_probability_cutoff_; }
59
+ uint8_t get_probability_cutoff() const { return this->probability_cutoff_; }
60
+ void set_probability_cutoff(uint8_t probability_cutoff) { this->probability_cutoff_ = probability_cutoff; }
61
+
34
62
  protected:
63
+ /// @brief Allocates tensor and variable arenas and sets up the model interpreter
64
+ /// @return True if successful, false otherwise
65
+ bool load_model_();
66
+ /// @brief Returns true if successfully registered the streaming model's TensorFlow operations
67
+ bool register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver);
68
+
69
+ tflite::MicroMutableOpResolver<20> streaming_op_resolver_;
70
+
71
+ bool loaded_{false};
72
+ bool enabled_{true};
73
+ bool unprocessed_probability_status_{false};
35
74
  uint8_t current_stride_step_{0};
75
+ int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION};
36
76
 
37
- float probability_cutoff_;
77
+ uint8_t default_probability_cutoff_;
78
+ uint8_t probability_cutoff_;
38
79
  size_t sliding_window_size_;
80
+
39
81
  size_t last_n_index_{0};
40
82
  size_t tensor_arena_size_;
41
83
  std::vector<uint8_t> recent_streaming_probabilities_;
@@ -50,32 +92,62 @@ class StreamingModel {
50
92
 
51
93
  class WakeWordModel final : public StreamingModel {
52
94
  public:
53
- WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
54
- const std::string &wake_word, size_t tensor_arena_size);
95
+ /// @brief Constructs a wake word model object
96
+ /// @param id (std::string) identifier for this model
97
+ /// @param model_start (const uint8_t *) pointer to the start of the model's TFLite FlatBuffer
98
+ /// @param default_probability_cutoff (uint8_t) probability cutoff for acceping the wake word has been said
99
+ /// @param sliding_window_average_size (size_t) the length of the sliding window computing the mean rolling
100
+ /// probability
101
+ /// @param wake_word (std::string) Friendly name of the wake word
102
+ /// @param tensor_arena_size (size_t) Size in bytes for allocating the tensor arena
103
+ /// @param default_enabled (bool) If true, it will be enabled by default on first boot
104
+ /// @param internal_only (bool) If true, the model will not be exposed to HomeAssistant as an available model
105
+ WakeWordModel(const std::string &id, const uint8_t *model_start, uint8_t default_probability_cutoff,
106
+ size_t sliding_window_average_size, const std::string &wake_word, size_t tensor_arena_size,
107
+ bool default_enabled, bool internal_only);
55
108
 
56
109
  void log_model_config() override;
57
110
 
58
111
  /// @brief Checks for the wake word by comparing the mean probability in the sliding window with the probability
59
112
  /// cutoff
60
113
  /// @return True if wake word is detected, false otherwise
61
- bool determine_detected() override;
114
+ DetectionEvent determine_detected() override;
62
115
 
116
+ const std::string &get_id() const { return this->id_; }
63
117
  const std::string &get_wake_word() const { return this->wake_word_; }
64
118
 
119
+ void add_trained_language(const std::string &language) { this->trained_languages_.push_back(language); }
120
+ const std::vector<std::string> &get_trained_languages() const { return this->trained_languages_; }
121
+
122
+ /// @brief Enable the model and save to flash. The next performing_streaming_inference call will load it.
123
+ void enable() override;
124
+
125
+ /// @brief Disable the model and save to flash. The next performing_streaming_inference call will unload it.
126
+ void disable() override;
127
+
128
+ bool get_internal_only() { return this->internal_only_; }
129
+
65
130
  protected:
131
+ std::string id_;
66
132
  std::string wake_word_;
133
+ std::vector<std::string> trained_languages_;
134
+
135
+ bool internal_only_;
136
+
137
+ ESPPreferenceObject pref_;
67
138
  };
68
139
 
69
140
  class VADModel final : public StreamingModel {
70
141
  public:
71
- VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, size_t tensor_arena_size);
142
+ VADModel(const uint8_t *model_start, uint8_t default_probability_cutoff, size_t sliding_window_size,
143
+ size_t tensor_arena_size);
72
144
 
73
145
  void log_model_config() override;
74
146
 
75
147
  /// @brief Checks for voice activity by comparing the max probability in the sliding window with the probability
76
148
  /// cutoff
77
149
  /// @return True if voice activity is detected, false otherwise
78
- bool determine_detected() override;
150
+ DetectionEvent determine_detected() override;
79
151
  };
80
152
 
81
153
  } // namespace micro_wake_word
@@ -1,12 +1,21 @@
1
1
  from esphome import automation
2
2
  from esphome.automation import maybe_simple_id
3
3
  import esphome.codegen as cg
4
+ from esphome.components import audio
4
5
  import esphome.config_validation as cv
5
- from esphome.const import CONF_ID, CONF_TRIGGER_ID
6
+ from esphome.const import (
7
+ CONF_BITS_PER_SAMPLE,
8
+ CONF_CHANNELS,
9
+ CONF_GAIN_FACTOR,
10
+ CONF_ID,
11
+ CONF_MICROPHONE,
12
+ CONF_TRIGGER_ID,
13
+ )
6
14
  from esphome.core import CORE
7
15
  from esphome.coroutine import coroutine_with_priority
8
16
 
9
- CODEOWNERS = ["@jesserockz"]
17
+ AUTO_LOAD = ["audio"]
18
+ CODEOWNERS = ["@jesserockz", "@kahrendt"]
10
19
 
11
20
  IS_PLATFORM_COMPONENT = True
12
21
 
@@ -15,6 +24,7 @@ CONF_ON_DATA = "on_data"
15
24
  microphone_ns = cg.esphome_ns.namespace("microphone")
16
25
 
17
26
  Microphone = microphone_ns.class_("Microphone")
27
+ MicrophoneSource = microphone_ns.class_("MicrophoneSource")
18
28
 
19
29
  CaptureAction = microphone_ns.class_(
20
30
  "CaptureAction", automation.Action, cg.Parented.template(Microphone)
@@ -22,16 +32,23 @@ CaptureAction = microphone_ns.class_(
22
32
  StopCaptureAction = microphone_ns.class_(
23
33
  "StopCaptureAction", automation.Action, cg.Parented.template(Microphone)
24
34
  )
35
+ MuteAction = microphone_ns.class_(
36
+ "MuteAction", automation.Action, cg.Parented.template(Microphone)
37
+ )
38
+ UnmuteAction = microphone_ns.class_(
39
+ "UnmuteAction", automation.Action, cg.Parented.template(Microphone)
40
+ )
25
41
 
26
42
 
27
43
  DataTrigger = microphone_ns.class_(
28
44
  "DataTrigger",
29
- automation.Trigger.template(cg.std_vector.template(cg.int16).operator("ref")),
45
+ automation.Trigger.template(cg.std_vector.template(cg.uint8).operator("ref")),
30
46
  )
31
47
 
32
48
  IsCapturingCondition = microphone_ns.class_(
33
49
  "IsCapturingCondition", automation.Condition
34
50
  )
51
+ IsMutedCondition = microphone_ns.class_("IsMutedCondition", automation.Condition)
35
52
 
36
53
 
37
54
  async def setup_microphone_core_(var, config):
@@ -39,7 +56,7 @@ async def setup_microphone_core_(var, config):
39
56
  trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
40
57
  await automation.build_automation(
41
58
  trigger,
42
- [(cg.std_vector.template(cg.int16).operator("ref").operator("const"), "x")],
59
+ [(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")],
43
60
  conf,
44
61
  )
45
62
 
@@ -50,7 +67,7 @@ async def register_microphone(var, config):
50
67
  await setup_microphone_core_(var, config)
51
68
 
52
69
 
53
- MICROPHONE_SCHEMA = cv.Schema(
70
+ MICROPHONE_SCHEMA = cv.Schema.extend(audio.AUDIO_COMPONENT_SCHEMA).extend(
54
71
  {
55
72
  cv.Optional(CONF_ON_DATA): automation.validate_automation(
56
73
  {
@@ -64,7 +81,110 @@ MICROPHONE_SCHEMA = cv.Schema(
64
81
  MICROPHONE_ACTION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(Microphone)})
65
82
 
66
83
 
67
- async def media_player_action(config, action_id, template_arg, args):
84
+ def microphone_source_schema(
85
+ min_bits_per_sample: int = 16,
86
+ max_bits_per_sample: int = 16,
87
+ min_channels: int = 1,
88
+ max_channels: int = 1,
89
+ ):
90
+ """Schema for a microphone source
91
+
92
+ Components requesting microphone data should use this schema instead of accessing a microphone directly.
93
+
94
+ Args:
95
+ min_bits_per_sample (int, optional): Minimum number of bits per sample the requesting component supports. Defaults to 16.
96
+ max_bits_per_sample (int, optional): Maximum number of bits per sample the requesting component supports. Defaults to 16.
97
+ min_channels (int, optional): Minimum number of channels the requesting component supports. Defaults to 1.
98
+ max_channels (int, optional): Maximum number of channels the requesting component supports. Defaults to 1.
99
+ """
100
+
101
+ def _validate_unique_channels(config):
102
+ if len(config) != len(set(config)):
103
+ raise cv.Invalid("Channels must be unique")
104
+ return config
105
+
106
+ return cv.All(
107
+ automation.maybe_conf(
108
+ CONF_MICROPHONE,
109
+ {
110
+ cv.GenerateID(CONF_ID): cv.declare_id(MicrophoneSource),
111
+ cv.GenerateID(CONF_MICROPHONE): cv.use_id(Microphone),
112
+ cv.Optional(CONF_BITS_PER_SAMPLE, default=16): cv.int_range(
113
+ min_bits_per_sample, max_bits_per_sample
114
+ ),
115
+ cv.Optional(CONF_CHANNELS, default="0"): cv.All(
116
+ cv.ensure_list(cv.int_range(0, 7)),
117
+ cv.Length(min=min_channels, max=max_channels),
118
+ _validate_unique_channels,
119
+ ),
120
+ cv.Optional(CONF_GAIN_FACTOR, default="1"): cv.int_range(1, 64),
121
+ },
122
+ ),
123
+ )
124
+
125
+
126
+ def final_validate_microphone_source_schema(
127
+ component_name: str, sample_rate: int = cv.UNDEFINED
128
+ ):
129
+ """Validates that the microphone source can provide audio in the correct format. In particular it validates the sample rate and the enabled channels.
130
+
131
+ Note that:
132
+ - MicrophoneSource class automatically handles converting bits per sample, so no need to validate
133
+ - microphone_source_schema already validates that channels are unique and specifies the max number of channels the component supports
134
+
135
+ Args:
136
+ component_name (str): The name of the component requesting mic audio
137
+ sample_rate (int, optional): The sample rate the component requesting mic audio requires
138
+ """
139
+
140
+ def _validate_audio_compatability(config):
141
+ if sample_rate is not cv.UNDEFINED:
142
+ # Issues require changing the microphone configuration
143
+ # - Verifies sample rates match
144
+ audio.final_validate_audio_schema(
145
+ component_name,
146
+ audio_device=CONF_MICROPHONE,
147
+ sample_rate=sample_rate,
148
+ audio_device_issue=True,
149
+ )(config)
150
+
151
+ # Issues require changing the MicrophoneSource configuration
152
+ # - Verifies that each of the enabled channels are available
153
+ audio.final_validate_audio_schema(
154
+ component_name,
155
+ audio_device=CONF_MICROPHONE,
156
+ enabled_channels=config[CONF_CHANNELS],
157
+ audio_device_issue=False,
158
+ )(config)
159
+
160
+ return config
161
+
162
+ return _validate_audio_compatability
163
+
164
+
165
+ async def microphone_source_to_code(config, passive=False):
166
+ """Creates a MicrophoneSource variable for codegen.
167
+
168
+ Setting passive to true makes the MicrophoneSource never start/stop the microphone, but only receives audio when another component has actively started the Microphone. If false, then the microphone needs to be explicitly started/stopped.
169
+
170
+ Args:
171
+ config (Schema): Created with `microphone_source_schema` specifying bits per sample, channels, and gain factor
172
+ passive (bool): Enable passive mode for the MicrophoneSource
173
+ """
174
+ mic = await cg.get_variable(config[CONF_MICROPHONE])
175
+ mic_source = cg.new_Pvariable(
176
+ config[CONF_ID],
177
+ mic,
178
+ config[CONF_BITS_PER_SAMPLE],
179
+ config[CONF_GAIN_FACTOR],
180
+ passive,
181
+ )
182
+ for channel in config[CONF_CHANNELS]:
183
+ cg.add(mic_source.add_channel(channel))
184
+ return mic_source
185
+
186
+
187
+ async def microphone_action(config, action_id, template_arg, args):
68
188
  var = cg.new_Pvariable(action_id, template_arg)
69
189
  await cg.register_parented(var, config[CONF_ID])
70
190
  return var
@@ -72,15 +192,25 @@ async def media_player_action(config, action_id, template_arg, args):
72
192
 
73
193
  automation.register_action(
74
194
  "microphone.capture", CaptureAction, MICROPHONE_ACTION_SCHEMA
75
- )(media_player_action)
195
+ )(microphone_action)
76
196
 
77
197
  automation.register_action(
78
198
  "microphone.stop_capture", StopCaptureAction, MICROPHONE_ACTION_SCHEMA
79
- )(media_player_action)
199
+ )(microphone_action)
200
+
201
+ automation.register_action("microphone.mute", MuteAction, MICROPHONE_ACTION_SCHEMA)(
202
+ microphone_action
203
+ )
204
+ automation.register_action("microphone.unmute", UnmuteAction, MICROPHONE_ACTION_SCHEMA)(
205
+ microphone_action
206
+ )
80
207
 
81
208
  automation.register_condition(
82
209
  "microphone.is_capturing", IsCapturingCondition, MICROPHONE_ACTION_SCHEMA
83
- )(media_player_action)
210
+ )(microphone_action)
211
+ automation.register_condition(
212
+ "microphone.is_muted", IsMutedCondition, MICROPHONE_ACTION_SCHEMA
213
+ )(microphone_action)
84
214
 
85
215
 
86
216
  @coroutine_with_priority(100.0)
@@ -16,10 +16,17 @@ template<typename... Ts> class StopCaptureAction : public Action<Ts...>, public
16
16
  void play(Ts... x) override { this->parent_->stop(); }
17
17
  };
18
18
 
19
- class DataTrigger : public Trigger<const std::vector<int16_t> &> {
19
+ template<typename... Ts> class MuteAction : public Action<Ts...>, public Parented<Microphone> {
20
+ void play(Ts... x) override { this->parent_->set_mute_state(true); }
21
+ };
22
+ template<typename... Ts> class UnmuteAction : public Action<Ts...>, public Parented<Microphone> {
23
+ void play(Ts... x) override { this->parent_->set_mute_state(false); }
24
+ };
25
+
26
+ class DataTrigger : public Trigger<const std::vector<uint8_t> &> {
20
27
  public:
21
28
  explicit DataTrigger(Microphone *mic) {
22
- mic->add_data_callback([this](const std::vector<int16_t> &data) { this->trigger(data); });
29
+ mic->add_data_callback([this](const std::vector<uint8_t> &data) { this->trigger(data); });
23
30
  }
24
31
  };
25
32
 
@@ -28,5 +35,10 @@ template<typename... Ts> class IsCapturingCondition : public Condition<Ts...>, p
28
35
  bool check(Ts... x) override { return this->parent_->is_running(); }
29
36
  };
30
37
 
38
+ template<typename... Ts> class IsMutedCondition : public Condition<Ts...>, public Parented<Microphone> {
39
+ public:
40
+ bool check(Ts... x) override { return this->parent_->get_mute_state(); }
41
+ };
42
+
31
43
  } // namespace microphone
32
44
  } // namespace esphome
@@ -0,0 +1,21 @@
1
+ #include "microphone.h"
2
+
3
+ namespace esphome {
4
+ namespace microphone {
5
+
6
+ void Microphone::add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) {
7
+ std::function<void(const std::vector<uint8_t> &)> mute_handled_callback =
8
+ [this, data_callback](const std::vector<uint8_t> &data) { data_callback(this->silence_audio_(data)); };
9
+ this->data_callbacks_.add(std::move(mute_handled_callback));
10
+ }
11
+
12
+ std::vector<uint8_t> Microphone::silence_audio_(std::vector<uint8_t> data) {
13
+ if (this->mute_state_) {
14
+ std::memset((void *) data.data(), 0, data.size());
15
+ }
16
+
17
+ return data;
18
+ }
19
+
20
+ } // namespace microphone
21
+ } // namespace esphome
@@ -1,5 +1,7 @@
1
1
  #pragma once
2
2
 
3
+ #include "esphome/components/audio/audio.h"
4
+
3
5
  #include <cstddef>
4
6
  #include <cstdint>
5
7
  #include <functional>
@@ -20,18 +22,25 @@ class Microphone {
20
22
  public:
21
23
  virtual void start() = 0;
22
24
  virtual void stop() = 0;
23
- void add_data_callback(std::function<void(const std::vector<int16_t> &)> &&data_callback) {
24
- this->data_callbacks_.add(std::move(data_callback));
25
- }
26
- virtual size_t read(int16_t *buf, size_t len) = 0;
25
+ void add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback);
27
26
 
28
27
  bool is_running() const { return this->state_ == STATE_RUNNING; }
29
28
  bool is_stopped() const { return this->state_ == STATE_STOPPED; }
30
29
 
30
+ void set_mute_state(bool is_muted) { this->mute_state_ = is_muted; }
31
+ bool get_mute_state() { return this->mute_state_; }
32
+
33
+ audio::AudioStreamInfo get_audio_stream_info() { return this->audio_stream_info_; }
34
+
31
35
  protected:
36
+ std::vector<uint8_t> silence_audio_(std::vector<uint8_t> data);
37
+
32
38
  State state_{STATE_STOPPED};
39
+ bool mute_state_{false};
40
+
41
+ audio::AudioStreamInfo audio_stream_info_;
33
42
 
34
- CallbackManager<void(const std::vector<int16_t> &)> data_callbacks_{};
43
+ CallbackManager<void(const std::vector<uint8_t> &)> data_callbacks_{};
35
44
  };
36
45
 
37
46
  } // namespace microphone
@@ -0,0 +1,95 @@
1
+ #include "microphone_source.h"
2
+
3
+ namespace esphome {
4
+ namespace microphone {
5
+
6
+ static const int32_t Q25_MAX_VALUE = (1 << 25) - 1;
7
+ static const int32_t Q25_MIN_VALUE = ~Q25_MAX_VALUE;
8
+
9
+ void MicrophoneSource::add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) {
10
+ std::function<void(const std::vector<uint8_t> &)> filtered_callback =
11
+ [this, data_callback](const std::vector<uint8_t> &data) {
12
+ if (this->enabled_ || this->passive_) {
13
+ if (this->processed_samples_.use_count() == 0) {
14
+ // Create vector if its unused
15
+ this->processed_samples_ = std::make_shared<std::vector<uint8_t>>();
16
+ }
17
+
18
+ // Take temporary ownership of samples vector to avoid deallaction before the callback finishes
19
+ std::shared_ptr<std::vector<uint8_t>> output_samples = this->processed_samples_;
20
+ this->process_audio_(data, *output_samples);
21
+ data_callback(*output_samples);
22
+ }
23
+ };
24
+ this->mic_->add_data_callback(std::move(filtered_callback));
25
+ }
26
+
27
+ audio::AudioStreamInfo MicrophoneSource::get_audio_stream_info() {
28
+ return audio::AudioStreamInfo(this->bits_per_sample_, this->channels_.count(),
29
+ this->mic_->get_audio_stream_info().get_sample_rate());
30
+ }
31
+
32
+ void MicrophoneSource::start() {
33
+ if (!this->enabled_ && !this->passive_) {
34
+ this->enabled_ = true;
35
+ this->mic_->start();
36
+ }
37
+ }
38
+
39
+ void MicrophoneSource::stop() {
40
+ if (this->enabled_ && !this->passive_) {
41
+ this->enabled_ = false;
42
+ this->mic_->stop();
43
+ this->processed_samples_.reset();
44
+ }
45
+ }
46
+
47
+ void MicrophoneSource::process_audio_(const std::vector<uint8_t> &data, std::vector<uint8_t> &filtered_data) {
48
+ // - Bit depth conversions are obtained by truncating bits or padding with zeros - no dithering is applied.
49
+ // - In the comments, Qxx refers to a fixed point number with xx bits of precision for representing fractional values.
50
+ // For example, audio with a bit depth of 16 can store a sample in a int16, which can be considered a Q15 number.
51
+ // - All samples are converted to Q25 before applying the gain factor - this results in a small precision loss for
52
+ // data with 32 bits per sample. Since the maximum gain factor is 64 = (1<<6), this ensures that applying the gain
53
+ // will never overflow a 32 bit signed integer. This still retains more bit depth than what is audibly noticeable.
54
+ // - Loops for reading/writing data buffers are unrolled, assuming little endian, for a small performance increase.
55
+
56
+ const size_t source_bytes_per_sample = this->mic_->get_audio_stream_info().samples_to_bytes(1);
57
+ const uint32_t source_channels = this->mic_->get_audio_stream_info().get_channels();
58
+
59
+ const size_t source_bytes_per_frame = this->mic_->get_audio_stream_info().frames_to_bytes(1);
60
+
61
+ const uint32_t total_frames = this->mic_->get_audio_stream_info().bytes_to_frames(data.size());
62
+ const size_t target_bytes_per_sample = (this->bits_per_sample_ + 7) / 8;
63
+ const size_t target_bytes_per_frame = target_bytes_per_sample * this->channels_.count();
64
+
65
+ filtered_data.resize(target_bytes_per_frame * total_frames);
66
+
67
+ uint8_t *current_data = filtered_data.data();
68
+
69
+ for (uint32_t frame_index = 0; frame_index < total_frames; ++frame_index) {
70
+ for (uint32_t channel_index = 0; channel_index < source_channels; ++channel_index) {
71
+ if (this->channels_.test(channel_index)) {
72
+ // Channel's current sample is included in the target mask. Convert bits per sample, if necessary.
73
+
74
+ const uint32_t sample_index = frame_index * source_bytes_per_frame + channel_index * source_bytes_per_sample;
75
+
76
+ int32_t sample = audio::unpack_audio_sample_to_q31(&data[sample_index], source_bytes_per_sample); // Q31
77
+ sample >>= 6; // Q31 -> Q25
78
+
79
+ // Apply gain using multiplication
80
+ sample *= this->gain_factor_; // Q25
81
+
82
+ // Clamp ``sample`` in case gain multiplication overflows 25 bits
83
+ sample = clamp<int32_t>(sample, Q25_MIN_VALUE, Q25_MAX_VALUE); // Q25
84
+
85
+ sample *= (1 << 6); // Q25 -> Q31
86
+
87
+ audio::pack_q31_as_audio_sample(sample, current_data, target_bytes_per_sample);
88
+ current_data = current_data + target_bytes_per_sample;
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ } // namespace microphone
95
+ } // namespace esphome
@@ -0,0 +1,80 @@
1
+ #pragma once
2
+
3
+ #include "microphone.h"
4
+
5
+ #include "esphome/components/audio/audio.h"
6
+
7
+ #include <bitset>
8
+ #include <cstddef>
9
+ #include <cstdint>
10
+ #include <functional>
11
+ #include <vector>
12
+
13
+ namespace esphome {
14
+ namespace microphone {
15
+
16
+ static const int32_t MAX_GAIN_FACTOR = 64;
17
+
18
+ class MicrophoneSource {
19
+ /*
20
+ * @brief Helper class that handles converting raw microphone data to a requested format.
21
+ * Components requesting microphone audio should register a callback through this class instead of registering a
22
+ * callback directly with the microphone if a particular format is required.
23
+ *
24
+ * Raw microphone data may have a different number of bits per sample and number of channels than the requesting
25
+ * component needs. This class handles the conversion by:
26
+ * - Internally adds a callback to receive the raw microphone data
27
+ * - The ``process_audio_`` handles the raw data
28
+ * - Only the channels set in the ``channels_`` bitset are passed through
29
+ * - Passed through samples have the bits per sample converted
30
+ * - A gain factor is optionally applied to increase the volume - audio may clip!
31
+ * - The processed audio is passed to the callback of the component requesting microphone data
32
+ * - It tracks an internal enabled state, so it ignores raw microphone data when the component requesting
33
+ * microphone data is not actively requesting audio.
34
+ *
35
+ * Note that this class cannot convert sample rates!
36
+ */
37
+ public:
38
+ MicrophoneSource(Microphone *mic, uint8_t bits_per_sample, int32_t gain_factor, bool passive)
39
+ : mic_(mic), bits_per_sample_(bits_per_sample), gain_factor_(gain_factor), passive_(passive) {}
40
+
41
+ /// @brief Enables a channel to be processed through the callback.
42
+ ///
43
+ /// If the microphone component only has reads from one channel, it is always in channel number 0, regardless if it
44
+ /// represents left or right. If the microphone reads from both left and right, channel number 0 and 1 represent the
45
+ /// left and right channels respectively.
46
+ ///
47
+ /// @param channel 0-indexed channel number to enable
48
+ void add_channel(uint8_t channel) { this->channels_.set(channel); }
49
+
50
+ void add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback);
51
+
52
+ void set_gain_factor(int32_t gain_factor) { this->gain_factor_ = clamp<int32_t>(gain_factor, 1, MAX_GAIN_FACTOR); }
53
+ int32_t get_gain_factor() { return this->gain_factor_; }
54
+
55
+ /// @brief Gets the AudioStreamInfo of the data after processing
56
+ /// @return audio::AudioStreamInfo with the configured bits per sample, configured channel count, and source
57
+ /// microphone's sample rate
58
+ audio::AudioStreamInfo get_audio_stream_info();
59
+
60
+ void start();
61
+ void stop();
62
+ bool is_passive() const { return this->passive_; }
63
+ bool is_running() const { return (this->mic_->is_running() && (this->enabled_ || this->passive_)); }
64
+ bool is_stopped() const { return !this->is_running(); };
65
+
66
+ protected:
67
+ void process_audio_(const std::vector<uint8_t> &data, std::vector<uint8_t> &filtered_data);
68
+
69
+ std::shared_ptr<std::vector<uint8_t>> processed_samples_;
70
+
71
+ Microphone *mic_;
72
+ uint8_t bits_per_sample_;
73
+ std::bitset<8> channels_;
74
+ int32_t gain_factor_;
75
+ bool enabled_{false};
76
+ bool passive_; // Only pass audio if ``mic_`` is already running
77
+ };
78
+
79
+ } // namespace microphone
80
+ } // namespace esphome