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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
  2. esphome/components/adc/__init__.py +51 -34
  3. esphome/components/airthings_wave_base/__init__.py +1 -1
  4. esphome/components/alarm_control_panel/__init__.py +37 -2
  5. esphome/components/am43/cover/__init__.py +4 -5
  6. esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
  7. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
  8. esphome/components/analog_threshold/binary_sensor.py +10 -8
  9. esphome/components/anova/climate.py +4 -5
  10. esphome/components/api/__init__.py +25 -8
  11. esphome/components/api/api_connection.cpp +77 -10
  12. esphome/components/api/api_connection.h +6 -1
  13. esphome/components/api/api_frame_helper.cpp +98 -130
  14. esphome/components/api/api_frame_helper.h +12 -2
  15. esphome/components/api/api_noise_context.h +13 -4
  16. esphome/components/api/api_pb2.cpp +1422 -1
  17. esphome/components/api/api_pb2.h +255 -1
  18. esphome/components/api/api_pb2_service.cpp +162 -49
  19. esphome/components/api/api_pb2_service.h +90 -51
  20. esphome/components/api/api_pb2_size.h +361 -0
  21. esphome/components/api/api_server.cpp +110 -34
  22. esphome/components/api/api_server.h +8 -0
  23. esphome/components/api/proto.h +38 -9
  24. esphome/components/as3935_i2c/as3935_i2c.h +0 -3
  25. esphome/components/as7341/as7341.h +1 -1
  26. esphome/components/atm90e32/__init__.py +1 -0
  27. esphome/components/atm90e32/atm90e32.cpp +576 -199
  28. esphome/components/atm90e32/atm90e32.h +128 -31
  29. esphome/components/atm90e32/atm90e32_reg.h +4 -2
  30. esphome/components/atm90e32/button/__init__.py +62 -10
  31. esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
  32. esphome/components/atm90e32/button/atm90e32_button.h +36 -4
  33. esphome/components/atm90e32/number/__init__.py +130 -0
  34. esphome/components/atm90e32/number/atm90e32_number.h +16 -0
  35. esphome/components/atm90e32/sensor.py +21 -4
  36. esphome/components/atm90e32/text_sensor/__init__.py +48 -0
  37. esphome/components/audio/__init__.py +96 -49
  38. esphome/components/audio/audio.h +48 -0
  39. esphome/components/audio/audio_decoder.cpp +1 -1
  40. esphome/components/audio/audio_resampler.cpp +2 -0
  41. esphome/components/audio/audio_resampler.h +1 -0
  42. esphome/components/ballu/climate.py +2 -9
  43. esphome/components/bang_bang/climate.py +5 -6
  44. esphome/components/bedjet/climate/__init__.py +3 -8
  45. esphome/components/bedjet/fan/__init__.py +2 -11
  46. esphome/components/binary/fan/__init__.py +13 -16
  47. esphome/components/binary_sensor/__init__.py +13 -10
  48. esphome/components/binary_sensor/binary_sensor.cpp +6 -10
  49. esphome/components/binary_sensor/binary_sensor.h +1 -1
  50. esphome/components/binary_sensor/filter.cpp +21 -21
  51. esphome/components/binary_sensor/filter.h +10 -10
  52. esphome/components/bl0906/constants.h +16 -16
  53. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  54. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  55. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +135 -21
  56. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  57. esphome/components/button/__init__.py +11 -8
  58. esphome/components/canbus/canbus.cpp +3 -0
  59. esphome/components/canbus/canbus.h +16 -0
  60. esphome/components/climate/__init__.py +35 -2
  61. esphome/components/climate/climate_mode.h +1 -1
  62. esphome/components/climate/climate_traits.h +63 -57
  63. esphome/components/climate_ir/__init__.py +57 -17
  64. esphome/components/climate_ir_lg/climate.py +2 -5
  65. esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
  66. esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
  67. esphome/components/color/__init__.py +2 -0
  68. esphome/components/const/__init__.py +5 -0
  69. esphome/components/coolix/climate.py +2 -9
  70. esphome/components/copy/cover/__init__.py +10 -9
  71. esphome/components/copy/fan/__init__.py +11 -9
  72. esphome/components/copy/lock/__init__.py +11 -9
  73. esphome/components/copy/text/__init__.py +9 -6
  74. esphome/components/cover/__init__.py +37 -2
  75. esphome/components/cst226/binary_sensor/__init__.py +28 -0
  76. esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
  77. esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
  78. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
  79. esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
  80. esphome/components/current_based/cover.py +37 -36
  81. esphome/components/daikin/climate.py +2 -9
  82. esphome/components/daikin/daikin.cpp +15 -9
  83. esphome/components/daikin/daikin.h +5 -5
  84. esphome/components/daikin_arc/climate.py +2 -7
  85. esphome/components/daikin_brc/climate.py +3 -5
  86. esphome/components/dallas_temp/dallas_temp.cpp +17 -24
  87. esphome/components/dallas_temp/dallas_temp.h +0 -1
  88. esphome/components/debug/debug_component.cpp +5 -0
  89. esphome/components/debug/debug_component.h +6 -0
  90. esphome/components/debug/debug_esp32.cpp +109 -254
  91. esphome/components/debug/sensor.py +14 -0
  92. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  93. esphome/components/delonghi/climate.py +2 -9
  94. esphome/components/demo/__init__.py +18 -20
  95. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  96. esphome/components/display/rect.cpp +4 -9
  97. esphome/components/display/rect.h +1 -1
  98. esphome/components/emmeti/climate.py +2 -9
  99. esphome/components/endstop/cover.py +17 -16
  100. esphome/components/esp32/__init__.py +60 -3
  101. esphome/components/esp32/core.cpp +11 -5
  102. esphome/components/esp32/gpio.cpp +86 -24
  103. esphome/components/esp32/gpio.py +15 -16
  104. esphome/components/esp32/gpio_esp32.py +1 -2
  105. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  106. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  107. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  108. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  109. esphome/components/esp32_ble/ble.cpp +1 -8
  110. esphome/components/esp32_ble/ble.h +5 -3
  111. esphome/components/esp32_ble/ble_advertising.h +1 -0
  112. esphome/components/esp32_ble_server/__init__.py +3 -0
  113. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  114. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  115. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  116. esphome/components/esp32_can/esp32_can.cpp +1 -1
  117. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  118. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  119. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  120. esphome/components/esp8266/gpio.cpp +69 -8
  121. esphome/components/event/__init__.py +13 -10
  122. esphome/components/factory_reset/switch/__init__.py +7 -21
  123. esphome/components/fan/__init__.py +52 -5
  124. esphome/components/fastled_base/__init__.py +1 -4
  125. esphome/components/fastled_base/fastled_light.cpp +1 -1
  126. esphome/components/feedback/cover.py +38 -33
  127. esphome/components/fujitsu_general/climate.py +2 -9
  128. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  129. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  130. esphome/components/gpio_expander/cached_gpio.h +22 -7
  131. esphome/components/gps/__init__.py +11 -2
  132. esphome/components/gps/gps.cpp +11 -8
  133. esphome/components/gps/gps.h +9 -6
  134. esphome/components/graph/__init__.py +1 -2
  135. esphome/components/gree/climate.py +4 -6
  136. esphome/components/gree/gree.cpp +16 -2
  137. esphome/components/gree/gree.h +2 -2
  138. esphome/components/haier/climate.py +37 -34
  139. esphome/components/hbridge/fan/__init__.py +19 -17
  140. esphome/components/he60r/cover.py +4 -5
  141. esphome/components/heatpumpir/climate.py +3 -6
  142. esphome/components/hitachi_ac344/climate.py +2 -9
  143. esphome/components/hitachi_ac424/climate.py +2 -9
  144. esphome/components/hlw8012/hlw8012.cpp +1 -1
  145. esphome/components/hm3301/hm3301.h +1 -1
  146. esphome/components/http_request/__init__.py +39 -6
  147. esphome/components/http_request/http_request.cpp +20 -0
  148. esphome/components/http_request/http_request.h +57 -15
  149. esphome/components/http_request/http_request_arduino.cpp +22 -6
  150. esphome/components/http_request/http_request_arduino.h +4 -3
  151. esphome/components/http_request/http_request_host.cpp +141 -0
  152. esphome/components/http_request/http_request_host.h +37 -0
  153. esphome/components/http_request/http_request_idf.cpp +35 -3
  154. esphome/components/http_request/http_request_idf.h +10 -3
  155. esphome/components/http_request/httplib.h +9691 -0
  156. esphome/components/http_request/update/__init__.py +11 -8
  157. esphome/components/i2c/i2c.h +4 -0
  158. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  159. esphome/components/i2s_audio/__init__.py +131 -22
  160. esphome/components/i2s_audio/i2s_audio.h +44 -4
  161. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  162. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  163. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  164. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  165. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  166. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  167. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  168. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  169. esphome/components/image/__init__.py +37 -17
  170. esphome/components/image/image.cpp +25 -8
  171. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  172. esphome/components/key_collector/__init__.py +35 -0
  173. esphome/components/key_collector/key_collector.cpp +8 -0
  174. esphome/components/key_collector/key_collector.h +10 -0
  175. esphome/components/ld2410/ld2410.h +1 -1
  176. esphome/components/ld2450/ld2450.h +1 -1
  177. esphome/components/light/__init__.py +57 -0
  178. esphome/components/lock/__init__.py +51 -4
  179. esphome/components/lock/automation.h +2 -13
  180. esphome/components/logger/__init__.py +21 -0
  181. esphome/components/logger/logger.cpp +125 -95
  182. esphome/components/logger/logger.h +160 -35
  183. esphome/components/logger/task_log_buffer.cpp +138 -0
  184. esphome/components/logger/task_log_buffer.h +69 -0
  185. esphome/components/lvgl/__init__.py +13 -5
  186. esphome/components/lvgl/automation.py +50 -1
  187. esphome/components/lvgl/defines.py +0 -1
  188. esphome/components/lvgl/lv_validation.py +10 -1
  189. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  190. esphome/components/lvgl/schemas.py +14 -14
  191. esphome/components/lvgl/text/__init__.py +1 -2
  192. esphome/components/lvgl/widgets/arc.py +7 -6
  193. esphome/components/lvgl/widgets/buttonmatrix.py +3 -3
  194. esphome/components/lvgl/widgets/checkbox.py +2 -2
  195. esphome/components/lvgl/widgets/dropdown.py +2 -1
  196. esphome/components/lvgl/widgets/img.py +15 -12
  197. esphome/components/mapping/__init__.py +134 -0
  198. esphome/components/max7219digit/max7219digit.cpp +27 -27
  199. esphome/components/mdns/__init__.py +11 -5
  200. esphome/components/mdns/mdns_component.cpp +11 -5
  201. esphome/components/mdns/mdns_component.h +3 -2
  202. esphome/components/mdns/mdns_esp32.cpp +4 -3
  203. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  204. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  205. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  206. esphome/components/media_player/__init__.py +40 -6
  207. esphome/components/micro_wake_word/__init__.py +99 -31
  208. esphome/components/micro_wake_word/automation.h +54 -0
  209. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  210. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  211. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  212. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  213. esphome/components/micro_wake_word/streaming_model.h +85 -13
  214. esphome/components/microphone/__init__.py +139 -9
  215. esphome/components/microphone/automation.h +14 -2
  216. esphome/components/microphone/microphone.cpp +21 -0
  217. esphome/components/microphone/microphone.h +14 -5
  218. esphome/components/microphone/microphone_source.cpp +95 -0
  219. esphome/components/microphone/microphone_source.h +80 -0
  220. esphome/components/mics_4514/sensor.py +25 -14
  221. esphome/components/midea/climate.py +3 -4
  222. esphome/components/midea_ir/climate.py +3 -5
  223. esphome/components/mipi_spi/__init__.py +15 -0
  224. esphome/components/mipi_spi/display.py +474 -0
  225. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  226. esphome/components/mipi_spi/mipi_spi.h +171 -0
  227. esphome/components/mipi_spi/models/__init__.py +65 -0
  228. esphome/components/mipi_spi/models/amoled.py +72 -0
  229. esphome/components/mipi_spi/models/commands.py +82 -0
  230. esphome/components/mipi_spi/models/cyd.py +10 -0
  231. esphome/components/mipi_spi/models/ili.py +749 -0
  232. esphome/components/mipi_spi/models/jc.py +260 -0
  233. esphome/components/mipi_spi/models/lanbon.py +15 -0
  234. esphome/components/mipi_spi/models/lilygo.py +60 -0
  235. esphome/components/mipi_spi/models/waveshare.py +139 -0
  236. esphome/components/mitsubishi/climate.py +2 -5
  237. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  238. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  239. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  240. esphome/components/mlx90393/sensor.py +5 -0
  241. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  242. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  243. esphome/components/mqtt/__init__.py +1 -1
  244. esphome/components/mqtt/mqtt_client.cpp +5 -1
  245. esphome/components/mqtt/mqtt_const.h +4 -0
  246. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  247. esphome/components/mqtt/mqtt_fan.h +2 -0
  248. esphome/components/network/__init__.py +1 -1
  249. esphome/components/nextion/base_component.py +17 -16
  250. esphome/components/nextion/display.py +11 -2
  251. esphome/components/nextion/nextion.cpp +39 -1
  252. esphome/components/nextion/nextion.h +50 -0
  253. esphome/components/noblex/climate.py +2 -9
  254. esphome/components/number/__init__.py +12 -9
  255. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  256. esphome/components/one_wire/one_wire_bus.h +14 -8
  257. esphome/components/online_image/bmp_image.cpp +48 -11
  258. esphome/components/online_image/bmp_image.h +2 -0
  259. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  260. esphome/components/opentherm/number/__init__.py +11 -20
  261. esphome/components/opentherm/sensor/__init__.py +3 -3
  262. esphome/components/opentherm/switch/__init__.py +3 -5
  263. esphome/components/output/lock/__init__.py +11 -9
  264. esphome/components/packages/__init__.py +33 -31
  265. esphome/components/packet_transport/__init__.py +201 -0
  266. esphome/components/packet_transport/binary_sensor.py +19 -0
  267. esphome/components/packet_transport/packet_transport.cpp +534 -0
  268. esphome/components/packet_transport/packet_transport.h +154 -0
  269. esphome/components/packet_transport/sensor.py +19 -0
  270. esphome/components/pca9685/pca9685_output.cpp +2 -1
  271. esphome/components/pid/climate.py +2 -4
  272. esphome/components/pm2005/__init__.py +1 -0
  273. esphome/components/pm2005/pm2005.cpp +123 -0
  274. esphome/components/pm2005/pm2005.h +46 -0
  275. esphome/components/pm2005/sensor.py +86 -0
  276. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  277. esphome/components/pmsa003i/pmsa003i.h +25 -25
  278. esphome/components/pmsx003/pmsx003.cpp +193 -229
  279. esphome/components/pmsx003/pmsx003.h +51 -33
  280. esphome/components/pmsx003/sensor.py +21 -11
  281. esphome/components/pn7150/pn7150.h +2 -2
  282. esphome/components/pn7160/pn7160.h +2 -2
  283. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  284. esphome/components/prometheus/prometheus_handler.h +17 -0
  285. esphome/components/psram/__init__.py +7 -5
  286. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  287. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  288. esphome/components/qspi_dbi/__init__.py +0 -1
  289. esphome/components/qspi_dbi/display.py +2 -1
  290. esphome/components/qspi_dbi/models.py +1 -2
  291. esphome/components/remote_base/__init__.py +91 -0
  292. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  293. esphome/components/remote_base/beo4_protocol.h +43 -0
  294. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  295. esphome/components/remote_base/gobox_protocol.h +54 -0
  296. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  297. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  298. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  299. esphome/components/scd30/sensor.py +2 -3
  300. esphome/components/scd4x/sensor.py +4 -5
  301. esphome/components/sdp3x/sensor.py +2 -1
  302. esphome/components/select/__init__.py +19 -20
  303. esphome/components/sen5x/sensor.py +1 -1
  304. esphome/components/sensor/__init__.py +158 -14
  305. esphome/components/sensor/filter.cpp +23 -0
  306. esphome/components/sensor/filter.h +22 -0
  307. esphome/components/sgp4x/sensor.py +1 -1
  308. esphome/components/sht4x/sht4x.cpp +43 -22
  309. esphome/components/sht4x/sht4x.h +1 -1
  310. esphome/components/sml/text_sensor/__init__.py +4 -6
  311. esphome/components/sound_level/__init__.py +0 -0
  312. esphome/components/sound_level/sensor.py +97 -0
  313. esphome/components/sound_level/sound_level.cpp +194 -0
  314. esphome/components/sound_level/sound_level.h +73 -0
  315. esphome/components/speaker/media_player/__init__.py +4 -8
  316. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  317. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  318. esphome/components/speaker/speaker.h +4 -7
  319. esphome/components/speed/fan/__init__.py +17 -16
  320. esphome/components/spi/spi.h +11 -1
  321. esphome/components/sprinkler/__init__.py +18 -19
  322. esphome/components/switch/__init__.py +32 -42
  323. esphome/components/syslog/__init__.py +41 -0
  324. esphome/components/syslog/esphome_syslog.cpp +49 -0
  325. esphome/components/syslog/esphome_syslog.h +27 -0
  326. esphome/components/tca9555/tca9555.cpp +11 -6
  327. esphome/components/tcl112/climate.py +2 -9
  328. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  329. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  330. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  331. esphome/components/template/cover/__init__.py +27 -21
  332. esphome/components/template/fan/__init__.py +14 -12
  333. esphome/components/template/lock/__init__.py +20 -25
  334. esphome/components/template/lock/automation.h +18 -0
  335. esphome/components/template/text/__init__.py +4 -3
  336. esphome/components/template/valve/__init__.py +32 -21
  337. esphome/components/template/valve/automation.h +24 -0
  338. esphome/components/text/__init__.py +32 -1
  339. esphome/components/text_sensor/__init__.py +24 -29
  340. esphome/components/thermostat/climate.py +5 -5
  341. esphome/components/time_based/cover.py +17 -16
  342. esphome/components/tm1638/switch/__init__.py +10 -7
  343. esphome/components/tormatic/cover.py +4 -5
  344. esphome/components/toshiba/climate.py +3 -5
  345. esphome/components/touchscreen/touchscreen.cpp +3 -1
  346. esphome/components/tt21100/touchscreen/tt21100.cpp +1 -1
  347. esphome/components/tuya/climate/__init__.py +5 -6
  348. esphome/components/tuya/cover/__init__.py +6 -11
  349. esphome/components/tuya/select/__init__.py +15 -5
  350. esphome/components/tuya/select/tuya_select.cpp +6 -1
  351. esphome/components/tuya/select/tuya_select.h +5 -1
  352. esphome/components/uart/packet_transport/__init__.py +20 -0
  353. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  354. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  355. esphome/components/udp/__init__.py +126 -128
  356. esphome/components/udp/automation.h +40 -0
  357. esphome/components/udp/binary_sensor.py +3 -25
  358. esphome/components/udp/packet_transport/__init__.py +29 -0
  359. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  360. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  361. esphome/components/udp/sensor.py +3 -25
  362. esphome/components/udp/udp_component.cpp +26 -470
  363. esphome/components/udp/udp_component.h +21 -128
  364. esphome/components/update/__init__.py +31 -1
  365. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  366. esphome/components/uptime/text_sensor/__init__.py +47 -7
  367. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  368. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  369. esphome/components/valve/__init__.py +34 -3
  370. esphome/components/valve/automation.h +1 -19
  371. esphome/components/vl53l0x/sensor.py +11 -0
  372. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  373. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  374. esphome/components/voice_assistant/__init__.py +36 -10
  375. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  376. esphome/components/voice_assistant/voice_assistant.h +26 -25
  377. esphome/components/waveshare_epaper/display.py +6 -0
  378. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  379. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  380. esphome/components/whirlpool/climate.py +3 -5
  381. esphome/components/whynter/climate.py +3 -5
  382. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  383. esphome/components/yashima/climate.py +6 -6
  384. esphome/components/zhlt01/climate.py +2 -7
  385. esphome/config_validation.py +38 -58
  386. esphome/const.py +15 -1
  387. esphome/core/__init__.py +2 -0
  388. esphome/core/application.cpp +1 -0
  389. esphome/core/application.h +4 -0
  390. esphome/core/automation.h +4 -3
  391. esphome/core/component.cpp +19 -3
  392. esphome/core/component.h +5 -0
  393. esphome/core/defines.h +23 -17
  394. esphome/core/macros.h +4 -0
  395. esphome/core/scheduler.cpp +3 -0
  396. esphome/cpp_generator.py +6 -2
  397. esphome/dashboard/web_server.py +3 -3
  398. esphome/helpers.py +39 -0
  399. esphome/loader.py +4 -0
  400. esphome/mqtt.py +21 -8
  401. esphome/platformio_api.py +1 -1
  402. esphome/schema_extractors.py +0 -1
  403. esphome/vscode.py +15 -0
  404. esphome/wizard.py +2 -2
  405. esphome/zeroconf.py +7 -3
  406. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/METADATA +10 -11
  407. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/RECORD +411 -352
  408. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/WHEEL +1 -1
  409. esphome/components/esp32_ble/const_esp32c6.h +0 -74
  410. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/entry_points.txt +0 -0
  411. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/licenses/LICENSE +0 -0
  412. {esphome-2025.4.1.dist-info → esphome-2025.5.0b2.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,4 @@
1
1
  #include "micro_wake_word.h"
2
- #include "streaming_model.h"
3
2
 
4
3
  #ifdef USE_ESP_IDF
5
4
 
@@ -7,41 +6,55 @@
7
6
  #include "esphome/core/helpers.h"
8
7
  #include "esphome/core/log.h"
9
8
 
10
- #include <frontend.h>
11
- #include <frontend_util.h>
9
+ #include "esphome/components/audio/audio_transfer_buffer.h"
12
10
 
13
- #include <tensorflow/lite/core/c/common.h>
14
- #include <tensorflow/lite/micro/micro_interpreter.h>
15
- #include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
16
-
17
- #include <cmath>
11
+ #ifdef USE_OTA
12
+ #include "esphome/components/ota/ota_backend.h"
13
+ #endif
18
14
 
19
15
  namespace esphome {
20
16
  namespace micro_wake_word {
21
17
 
22
18
  static const char *const TAG = "micro_wake_word";
23
19
 
24
- static const size_t SAMPLE_RATE_HZ = 16000; // 16 kHz
25
- static const size_t BUFFER_LENGTH = 64; // 0.064 seconds
26
- static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH;
27
- static const size_t INPUT_BUFFER_SIZE = 16 * SAMPLE_RATE_HZ / 1000; // 16ms * 16kHz / 1000ms
20
+ static const ssize_t DETECTION_QUEUE_LENGTH = 5;
21
+
22
+ static const size_t DATA_TIMEOUT_MS = 50;
23
+
24
+ static const uint32_t RING_BUFFER_DURATION_MS = 120;
25
+
26
+ static const uint32_t INFERENCE_TASK_STACK_SIZE = 3072;
27
+ static const UBaseType_t INFERENCE_TASK_PRIORITY = 3;
28
+
29
+ enum EventGroupBits : uint32_t {
30
+ COMMAND_STOP = (1 << 0), // Signals the inference task should stop
31
+
32
+ TASK_STARTING = (1 << 3),
33
+ TASK_RUNNING = (1 << 4),
34
+ TASK_STOPPING = (1 << 5),
35
+ TASK_STOPPED = (1 << 6),
36
+
37
+ ERROR_MEMORY = (1 << 9),
38
+ ERROR_INFERENCE = (1 << 10),
39
+
40
+ WARNING_FULL_RING_BUFFER = (1 << 13),
41
+
42
+ ERROR_BITS = ERROR_MEMORY | ERROR_INFERENCE,
43
+ ALL_BITS = 0xfffff, // 24 total bits available in an event group
44
+ };
28
45
 
29
46
  float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
30
47
 
31
48
  static const LogString *micro_wake_word_state_to_string(State state) {
32
49
  switch (state) {
33
- case State::IDLE:
34
- return LOG_STR("IDLE");
35
- case State::START_MICROPHONE:
36
- return LOG_STR("START_MICROPHONE");
37
- case State::STARTING_MICROPHONE:
38
- return LOG_STR("STARTING_MICROPHONE");
50
+ case State::STARTING:
51
+ return LOG_STR("STARTING");
39
52
  case State::DETECTING_WAKE_WORD:
40
53
  return LOG_STR("DETECTING_WAKE_WORD");
41
- case State::STOP_MICROPHONE:
42
- return LOG_STR("STOP_MICROPHONE");
43
- case State::STOPPING_MICROPHONE:
44
- return LOG_STR("STOPPING_MICROPHONE");
54
+ case State::STOPPING:
55
+ return LOG_STR("STOPPING");
56
+ case State::STOPPED:
57
+ return LOG_STR("STOPPED");
45
58
  default:
46
59
  return LOG_STR("UNKNOWN");
47
60
  }
@@ -51,7 +64,7 @@ void MicroWakeWord::dump_config() {
51
64
  ESP_LOGCONFIG(TAG, "microWakeWord:");
52
65
  ESP_LOGCONFIG(TAG, " models:");
53
66
  for (auto &model : this->wake_word_models_) {
54
- model.log_model_config();
67
+ model->log_model_config();
55
68
  }
56
69
  #ifdef USE_MICRO_WAKE_WORD_VAD
57
70
  this->vad_model_->log_model_config();
@@ -61,308 +74,318 @@ void MicroWakeWord::dump_config() {
61
74
  void MicroWakeWord::setup() {
62
75
  ESP_LOGCONFIG(TAG, "Setting up microWakeWord...");
63
76
 
64
- if (!this->register_streaming_ops_(this->streaming_op_resolver_)) {
77
+ this->frontend_config_.window.size_ms = FEATURE_DURATION_MS;
78
+ this->frontend_config_.window.step_size_ms = this->features_step_size_;
79
+ this->frontend_config_.filterbank.num_channels = PREPROCESSOR_FEATURE_SIZE;
80
+ this->frontend_config_.filterbank.lower_band_limit = FILTERBANK_LOWER_BAND_LIMIT;
81
+ this->frontend_config_.filterbank.upper_band_limit = FILTERBANK_UPPER_BAND_LIMIT;
82
+ this->frontend_config_.noise_reduction.smoothing_bits = NOISE_REDUCTION_SMOOTHING_BITS;
83
+ this->frontend_config_.noise_reduction.even_smoothing = NOISE_REDUCTION_EVEN_SMOOTHING;
84
+ this->frontend_config_.noise_reduction.odd_smoothing = NOISE_REDUCTION_ODD_SMOOTHING;
85
+ this->frontend_config_.noise_reduction.min_signal_remaining = NOISE_REDUCTION_MIN_SIGNAL_REMAINING;
86
+ this->frontend_config_.pcan_gain_control.enable_pcan = PCAN_GAIN_CONTROL_ENABLE_PCAN;
87
+ this->frontend_config_.pcan_gain_control.strength = PCAN_GAIN_CONTROL_STRENGTH;
88
+ this->frontend_config_.pcan_gain_control.offset = PCAN_GAIN_CONTROL_OFFSET;
89
+ this->frontend_config_.pcan_gain_control.gain_bits = PCAN_GAIN_CONTROL_GAIN_BITS;
90
+ this->frontend_config_.log_scale.enable_log = LOG_SCALE_ENABLE_LOG;
91
+ this->frontend_config_.log_scale.scale_shift = LOG_SCALE_SCALE_SHIFT;
92
+
93
+ this->event_group_ = xEventGroupCreate();
94
+ if (this->event_group_ == nullptr) {
95
+ ESP_LOGE(TAG, "Failed to create event group");
65
96
  this->mark_failed();
66
97
  return;
67
98
  }
68
99
 
69
- ESP_LOGCONFIG(TAG, "Micro Wake Word initialized");
100
+ this->detection_queue_ = xQueueCreate(DETECTION_QUEUE_LENGTH, sizeof(DetectionEvent));
101
+ if (this->detection_queue_ == nullptr) {
102
+ ESP_LOGE(TAG, "Failed to create detection event queue");
103
+ this->mark_failed();
104
+ return;
105
+ }
70
106
 
71
- this->frontend_config_.window.size_ms = FEATURE_DURATION_MS;
72
- this->frontend_config_.window.step_size_ms = this->features_step_size_;
73
- this->frontend_config_.filterbank.num_channels = PREPROCESSOR_FEATURE_SIZE;
74
- this->frontend_config_.filterbank.lower_band_limit = 125.0;
75
- this->frontend_config_.filterbank.upper_band_limit = 7500.0;
76
- this->frontend_config_.noise_reduction.smoothing_bits = 10;
77
- this->frontend_config_.noise_reduction.even_smoothing = 0.025;
78
- this->frontend_config_.noise_reduction.odd_smoothing = 0.06;
79
- this->frontend_config_.noise_reduction.min_signal_remaining = 0.05;
80
- this->frontend_config_.pcan_gain_control.enable_pcan = 1;
81
- this->frontend_config_.pcan_gain_control.strength = 0.95;
82
- this->frontend_config_.pcan_gain_control.offset = 80.0;
83
- this->frontend_config_.pcan_gain_control.gain_bits = 21;
84
- this->frontend_config_.log_scale.enable_log = 1;
85
- this->frontend_config_.log_scale.scale_shift = 6;
86
- }
107
+ this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
108
+ if (this->state_ == State::STOPPED) {
109
+ return;
110
+ }
111
+ std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
112
+ if (this->ring_buffer_.use_count() > 1) {
113
+ size_t bytes_free = temp_ring_buffer->free();
87
114
 
88
- void MicroWakeWord::add_wake_word_model(const uint8_t *model_start, float probability_cutoff,
89
- size_t sliding_window_average_size, const std::string &wake_word,
90
- size_t tensor_arena_size) {
91
- this->wake_word_models_.emplace_back(model_start, probability_cutoff, sliding_window_average_size, wake_word,
92
- tensor_arena_size);
115
+ if (bytes_free < data.size()) {
116
+ xEventGroupSetBits(this->event_group_, EventGroupBits::WARNING_FULL_RING_BUFFER);
117
+ temp_ring_buffer->reset();
118
+ }
119
+ temp_ring_buffer->write((void *) data.data(), data.size());
120
+ }
121
+ });
122
+
123
+ #ifdef USE_OTA
124
+ ota::get_global_ota_callback()->add_on_state_callback(
125
+ [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
126
+ if (state == ota::OTA_STARTED) {
127
+ this->suspend_task_();
128
+ } else if (state == ota::OTA_ERROR) {
129
+ this->resume_task_();
130
+ }
131
+ });
132
+ #endif
133
+ ESP_LOGCONFIG(TAG, "Micro Wake Word initialized");
93
134
  }
94
135
 
95
- #ifdef USE_MICRO_WAKE_WORD_VAD
96
- void MicroWakeWord::add_vad_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size,
97
- size_t tensor_arena_size) {
98
- this->vad_model_ = make_unique<VADModel>(model_start, probability_cutoff, sliding_window_size, tensor_arena_size);
99
- }
100
- #endif
136
+ void MicroWakeWord::inference_task(void *params) {
137
+ MicroWakeWord *this_mww = (MicroWakeWord *) params;
101
138
 
102
- void MicroWakeWord::loop() {
103
- switch (this->state_) {
104
- case State::IDLE:
105
- break;
106
- case State::START_MICROPHONE:
107
- ESP_LOGD(TAG, "Starting Microphone");
108
- this->microphone_->start();
109
- this->set_state_(State::STARTING_MICROPHONE);
110
- this->high_freq_.start();
111
- break;
112
- case State::STARTING_MICROPHONE:
113
- if (this->microphone_->is_running()) {
114
- this->set_state_(State::DETECTING_WAKE_WORD);
115
- }
116
- break;
117
- case State::DETECTING_WAKE_WORD:
118
- while (!this->has_enough_samples_()) {
119
- this->read_microphone_();
139
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::TASK_STARTING);
140
+
141
+ { // Ensures any C++ objects fall out of scope to deallocate before deleting the task
142
+
143
+ const size_t new_bytes_to_process =
144
+ this_mww->microphone_source_->get_audio_stream_info().ms_to_bytes(this_mww->features_step_size_);
145
+ std::unique_ptr<audio::AudioSourceTransferBuffer> audio_buffer;
146
+ int8_t features_buffer[PREPROCESSOR_FEATURE_SIZE];
147
+
148
+ if (!(xEventGroupGetBits(this_mww->event_group_) & ERROR_BITS)) {
149
+ // Allocate audio transfer buffer
150
+ audio_buffer = audio::AudioSourceTransferBuffer::create(new_bytes_to_process);
151
+
152
+ if (audio_buffer == nullptr) {
153
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::ERROR_MEMORY);
120
154
  }
121
- this->update_model_probabilities_();
122
- if (this->detect_wake_words_()) {
123
- ESP_LOGD(TAG, "Wake Word '%s' Detected", (this->detected_wake_word_).c_str());
124
- this->detected_ = true;
125
- this->set_state_(State::STOP_MICROPHONE);
155
+ }
156
+
157
+ if (!(xEventGroupGetBits(this_mww->event_group_) & ERROR_BITS)) {
158
+ // Allocate ring buffer
159
+ std::shared_ptr<RingBuffer> temp_ring_buffer = RingBuffer::create(
160
+ this_mww->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS));
161
+ if (temp_ring_buffer.use_count() == 0) {
162
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::ERROR_MEMORY);
126
163
  }
127
- break;
128
- case State::STOP_MICROPHONE:
129
- ESP_LOGD(TAG, "Stopping Microphone");
130
- this->microphone_->stop();
131
- this->set_state_(State::STOPPING_MICROPHONE);
132
- this->high_freq_.stop();
133
- this->unload_models_();
134
- this->deallocate_buffers_();
135
- break;
136
- case State::STOPPING_MICROPHONE:
137
- if (this->microphone_->is_stopped()) {
138
- this->set_state_(State::IDLE);
139
- if (this->detected_) {
140
- this->wake_word_detected_trigger_->trigger(this->detected_wake_word_);
141
- this->detected_ = false;
142
- this->detected_wake_word_ = "";
164
+ audio_buffer->set_source(temp_ring_buffer);
165
+ this_mww->ring_buffer_ = temp_ring_buffer;
166
+ }
167
+
168
+ if (!(xEventGroupGetBits(this_mww->event_group_) & ERROR_BITS)) {
169
+ this_mww->microphone_source_->start();
170
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::TASK_RUNNING);
171
+
172
+ while (!(xEventGroupGetBits(this_mww->event_group_) & COMMAND_STOP)) {
173
+ audio_buffer->transfer_data_from_source(pdMS_TO_TICKS(DATA_TIMEOUT_MS));
174
+
175
+ if (audio_buffer->available() < new_bytes_to_process) {
176
+ // Insufficient data to generate new spectrogram features, read more next iteration
177
+ continue;
143
178
  }
179
+
180
+ // Generate new spectrogram features
181
+ uint32_t processed_samples = this_mww->generate_features_(
182
+ (int16_t *) audio_buffer->get_buffer_start(), audio_buffer->available() / sizeof(int16_t), features_buffer);
183
+ audio_buffer->decrease_buffer_length(processed_samples * sizeof(int16_t));
184
+
185
+ // Run inference using the new spectorgram features
186
+ if (!this_mww->update_model_probabilities_(features_buffer)) {
187
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::ERROR_INFERENCE);
188
+ break;
189
+ }
190
+
191
+ // Process each model's probabilities and possibly send a Detection Event to the queue
192
+ this_mww->process_probabilities_();
144
193
  }
145
- break;
194
+ }
146
195
  }
147
- }
148
196
 
149
- void MicroWakeWord::start() {
150
- if (!this->is_ready()) {
151
- ESP_LOGW(TAG, "Wake word detection can't start as the component hasn't been setup yet");
152
- return;
153
- }
197
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::TASK_STOPPING);
154
198
 
155
- if (this->is_failed()) {
156
- ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs");
157
- return;
158
- }
199
+ this_mww->unload_models_();
200
+ this_mww->microphone_source_->stop();
201
+ FrontendFreeStateContents(&this_mww->frontend_state_);
159
202
 
160
- if (!this->load_models_() || !this->allocate_buffers_()) {
161
- ESP_LOGE(TAG, "Failed to load the wake word model(s) or allocate buffers");
162
- this->status_set_error();
163
- } else {
164
- this->status_clear_error();
203
+ xEventGroupSetBits(this_mww->event_group_, EventGroupBits::TASK_STOPPED);
204
+ while (true) {
205
+ // Continuously delay until the main loop deletes the task
206
+ delay(10);
165
207
  }
208
+ }
166
209
 
167
- if (this->status_has_error()) {
168
- ESP_LOGW(TAG, "Wake word component has an error. Please check logs");
169
- return;
210
+ std::vector<WakeWordModel *> MicroWakeWord::get_wake_words() {
211
+ std::vector<WakeWordModel *> external_wake_word_models;
212
+ for (auto *model : this->wake_word_models_) {
213
+ if (!model->get_internal_only()) {
214
+ external_wake_word_models.push_back(model);
215
+ }
170
216
  }
217
+ return external_wake_word_models;
218
+ }
171
219
 
172
- if (this->state_ != State::IDLE) {
173
- ESP_LOGW(TAG, "Wake word is already running");
174
- return;
175
- }
220
+ void MicroWakeWord::add_wake_word_model(WakeWordModel *model) { this->wake_word_models_.push_back(model); }
176
221
 
177
- this->reset_states_();
178
- this->set_state_(State::START_MICROPHONE);
222
+ #ifdef USE_MICRO_WAKE_WORD_VAD
223
+ void MicroWakeWord::add_vad_model(const uint8_t *model_start, uint8_t probability_cutoff, size_t sliding_window_size,
224
+ size_t tensor_arena_size) {
225
+ this->vad_model_ = make_unique<VADModel>(model_start, probability_cutoff, sliding_window_size, tensor_arena_size);
179
226
  }
227
+ #endif
180
228
 
181
- void MicroWakeWord::stop() {
182
- if (this->state_ == State::IDLE) {
183
- ESP_LOGW(TAG, "Wake word is already stopped");
184
- return;
185
- }
186
- if (this->state_ == State::STOPPING_MICROPHONE) {
187
- ESP_LOGW(TAG, "Wake word is already stopping");
188
- return;
229
+ void MicroWakeWord::suspend_task_() {
230
+ if (this->inference_task_handle_ != nullptr) {
231
+ vTaskSuspend(this->inference_task_handle_);
189
232
  }
190
- this->set_state_(State::STOP_MICROPHONE);
191
233
  }
192
234
 
193
- void MicroWakeWord::set_state_(State state) {
194
- ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(micro_wake_word_state_to_string(this->state_)),
195
- LOG_STR_ARG(micro_wake_word_state_to_string(state)));
196
- this->state_ = state;
235
+ void MicroWakeWord::resume_task_() {
236
+ if (this->inference_task_handle_ != nullptr) {
237
+ vTaskResume(this->inference_task_handle_);
238
+ }
197
239
  }
198
240
 
199
- size_t MicroWakeWord::read_microphone_() {
200
- size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
201
- if (bytes_read == 0) {
202
- return 0;
203
- }
241
+ void MicroWakeWord::loop() {
242
+ uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
204
243
 
205
- size_t bytes_free = this->ring_buffer_->free();
244
+ if (event_group_bits & EventGroupBits::ERROR_MEMORY) {
245
+ xEventGroupClearBits(this->event_group_, EventGroupBits::ERROR_MEMORY);
246
+ ESP_LOGE(TAG, "Encountered an error allocating buffers");
247
+ }
206
248
 
207
- if (bytes_free < bytes_read) {
208
- ESP_LOGW(TAG,
209
- "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). "
210
- "Resetting the ring buffer. Wake word detection accuracy will be reduced.",
211
- bytes_free, bytes_read);
249
+ if (event_group_bits & EventGroupBits::ERROR_INFERENCE) {
250
+ xEventGroupClearBits(this->event_group_, EventGroupBits::ERROR_INFERENCE);
251
+ ESP_LOGE(TAG, "Encountered an error while performing an inference");
252
+ }
212
253
 
213
- this->ring_buffer_->reset();
254
+ if (event_group_bits & EventGroupBits::WARNING_FULL_RING_BUFFER) {
255
+ xEventGroupClearBits(this->event_group_, EventGroupBits::WARNING_FULL_RING_BUFFER);
256
+ ESP_LOGW(TAG, "Not enough free bytes in ring buffer to store incoming audio data. Resetting the ring buffer. Wake "
257
+ "word detection accuracy will temporarily be reduced.");
214
258
  }
215
259
 
216
- return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
217
- }
260
+ if (event_group_bits & EventGroupBits::TASK_STARTING) {
261
+ ESP_LOGD(TAG, "Inference task has started, attempting to allocate memory for buffers");
262
+ xEventGroupClearBits(this->event_group_, EventGroupBits::TASK_STARTING);
263
+ }
218
264
 
219
- bool MicroWakeWord::allocate_buffers_() {
220
- ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
265
+ if (event_group_bits & EventGroupBits::TASK_RUNNING) {
266
+ ESP_LOGD(TAG, "Inference task is running");
221
267
 
222
- if (this->input_buffer_ == nullptr) {
223
- this->input_buffer_ = audio_samples_allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t));
224
- if (this->input_buffer_ == nullptr) {
225
- ESP_LOGE(TAG, "Could not allocate input buffer");
226
- return false;
227
- }
268
+ xEventGroupClearBits(this->event_group_, EventGroupBits::TASK_RUNNING);
269
+ this->set_state_(State::DETECTING_WAKE_WORD);
228
270
  }
229
271
 
230
- if (this->preprocessor_audio_buffer_ == nullptr) {
231
- this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(this->new_samples_to_get_());
232
- if (this->preprocessor_audio_buffer_ == nullptr) {
233
- ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer.");
234
- return false;
235
- }
272
+ if (event_group_bits & EventGroupBits::TASK_STOPPING) {
273
+ ESP_LOGD(TAG, "Inference task is stopping, deallocating buffers");
274
+ xEventGroupClearBits(this->event_group_, EventGroupBits::TASK_STOPPING);
236
275
  }
237
276
 
238
- if (this->ring_buffer_ == nullptr) {
239
- this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
240
- if (this->ring_buffer_ == nullptr) {
241
- ESP_LOGE(TAG, "Could not allocate ring buffer");
242
- return false;
243
- }
277
+ if ((event_group_bits & EventGroupBits::TASK_STOPPED)) {
278
+ ESP_LOGD(TAG, "Inference task is finished, freeing task resources");
279
+ vTaskDelete(this->inference_task_handle_);
280
+ this->inference_task_handle_ = nullptr;
281
+ xEventGroupClearBits(this->event_group_, ALL_BITS);
282
+ xQueueReset(this->detection_queue_);
283
+ this->set_state_(State::STOPPED);
244
284
  }
245
285
 
246
- return true;
247
- }
248
-
249
- void MicroWakeWord::deallocate_buffers_() {
250
- ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
251
- audio_samples_allocator.deallocate(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
252
- this->input_buffer_ = nullptr;
253
- audio_samples_allocator.deallocate(this->preprocessor_audio_buffer_, this->new_samples_to_get_());
254
- this->preprocessor_audio_buffer_ = nullptr;
255
- }
256
-
257
- bool MicroWakeWord::load_models_() {
258
- // Setup preprocesor feature generator
259
- if (!FrontendPopulateState(&this->frontend_config_, &this->frontend_state_, AUDIO_SAMPLE_FREQUENCY)) {
260
- ESP_LOGD(TAG, "Failed to populate frontend state");
261
- FrontendFreeStateContents(&this->frontend_state_);
262
- return false;
286
+ if ((this->pending_start_) && (this->state_ == State::STOPPED)) {
287
+ this->set_state_(State::STARTING);
288
+ this->pending_start_ = false;
263
289
  }
264
290
 
265
- // Setup streaming models
266
- for (auto &model : this->wake_word_models_) {
267
- if (!model.load_model(this->streaming_op_resolver_)) {
268
- ESP_LOGE(TAG, "Failed to initialize a wake word model.");
269
- return false;
270
- }
271
- }
272
- #ifdef USE_MICRO_WAKE_WORD_VAD
273
- if (!this->vad_model_->load_model(this->streaming_op_resolver_)) {
274
- ESP_LOGE(TAG, "Failed to initialize VAD model.");
275
- return false;
291
+ if ((this->pending_stop_) && (this->state_ == State::DETECTING_WAKE_WORD)) {
292
+ this->set_state_(State::STOPPING);
293
+ this->pending_stop_ = false;
276
294
  }
277
- #endif
278
295
 
279
- return true;
280
- }
296
+ switch (this->state_) {
297
+ case State::STARTING:
298
+ if ((this->inference_task_handle_ == nullptr) && !this->status_has_error()) {
299
+ // Setup preprocesor feature generator. If done in the task, it would lock the task to its initial core, as it
300
+ // uses floating point operations.
301
+ if (!FrontendPopulateState(&this->frontend_config_, &this->frontend_state_,
302
+ this->microphone_source_->get_audio_stream_info().get_sample_rate())) {
303
+ this->status_momentary_error(
304
+ "Failed to allocate buffers for spectrogram feature processor, attempting again in 1 second", 1000);
305
+ return;
306
+ }
281
307
 
282
- void MicroWakeWord::unload_models_() {
283
- FrontendFreeStateContents(&this->frontend_state_);
308
+ xTaskCreate(MicroWakeWord::inference_task, "mww", INFERENCE_TASK_STACK_SIZE, (void *) this,
309
+ INFERENCE_TASK_PRIORITY, &this->inference_task_handle_);
284
310
 
285
- for (auto &model : this->wake_word_models_) {
286
- model.unload_model();
311
+ if (this->inference_task_handle_ == nullptr) {
312
+ FrontendFreeStateContents(&this->frontend_state_); // Deallocate frontend state
313
+ this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000);
314
+ }
315
+ }
316
+ break;
317
+ case State::DETECTING_WAKE_WORD: {
318
+ DetectionEvent detection_event;
319
+ while (xQueueReceive(this->detection_queue_, &detection_event, 0)) {
320
+ if (detection_event.blocked_by_vad) {
321
+ ESP_LOGD(TAG, "Wake word model predicts '%s', but VAD model doesn't.", detection_event.wake_word->c_str());
322
+ } else {
323
+ constexpr float uint8_to_float_divisor =
324
+ 255.0f; // Converting a quantized uint8 probability to floating point
325
+ ESP_LOGD(TAG, "Detected '%s' with sliding average probability is %.2f and max probability is %.2f",
326
+ detection_event.wake_word->c_str(), (detection_event.average_probability / uint8_to_float_divisor),
327
+ (detection_event.max_probability / uint8_to_float_divisor));
328
+ this->wake_word_detected_trigger_->trigger(*detection_event.wake_word);
329
+ if (this->stop_after_detection_) {
330
+ this->stop();
331
+ }
332
+ }
333
+ }
334
+ break;
335
+ }
336
+ case State::STOPPING:
337
+ xEventGroupSetBits(this->event_group_, EventGroupBits::COMMAND_STOP);
338
+ break;
339
+ case State::STOPPED:
340
+ break;
287
341
  }
288
- #ifdef USE_MICRO_WAKE_WORD_VAD
289
- this->vad_model_->unload_model();
290
- #endif
291
342
  }
292
343
 
293
- void MicroWakeWord::update_model_probabilities_() {
294
- int8_t audio_features[PREPROCESSOR_FEATURE_SIZE];
295
-
296
- if (!this->generate_features_for_window_(audio_features)) {
344
+ void MicroWakeWord::start() {
345
+ if (!this->is_ready()) {
346
+ ESP_LOGW(TAG, "Wake word detection can't start as the component hasn't been setup yet");
297
347
  return;
298
348
  }
299
349
 
300
- // Increase the counter since the last positive detection
301
- this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
302
-
303
- for (auto &model : this->wake_word_models_) {
304
- // Perform inference
305
- model.perform_streaming_inference(audio_features);
350
+ if (this->is_failed()) {
351
+ ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs");
352
+ return;
306
353
  }
307
- #ifdef USE_MICRO_WAKE_WORD_VAD
308
- this->vad_model_->perform_streaming_inference(audio_features);
309
- #endif
310
- }
311
354
 
312
- bool MicroWakeWord::detect_wake_words_() {
313
- // Verify we have processed samples since the last positive detection
314
- if (this->ignore_windows_ < 0) {
315
- return false;
355
+ if (this->is_running()) {
356
+ ESP_LOGW(TAG, "Wake word detection is already running");
357
+ return;
316
358
  }
317
359
 
318
- #ifdef USE_MICRO_WAKE_WORD_VAD
319
- bool vad_state = this->vad_model_->determine_detected();
320
- #endif
321
-
322
- for (auto &model : this->wake_word_models_) {
323
- if (model.determine_detected()) {
324
- #ifdef USE_MICRO_WAKE_WORD_VAD
325
- if (vad_state) {
326
- #endif
327
- this->detected_wake_word_ = model.get_wake_word();
328
- return true;
329
- #ifdef USE_MICRO_WAKE_WORD_VAD
330
- } else {
331
- ESP_LOGD(TAG, "Wake word model predicts %s, but VAD model doesn't.", model.get_wake_word().c_str());
332
- }
333
- #endif
334
- }
335
- }
360
+ ESP_LOGD(TAG, "Starting wake word detection");
336
361
 
337
- return false;
362
+ this->pending_start_ = true;
363
+ this->pending_stop_ = false;
338
364
  }
339
365
 
340
- bool MicroWakeWord::has_enough_samples_() {
341
- return this->ring_buffer_->available() >=
342
- (this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)) * sizeof(int16_t);
343
- }
366
+ void MicroWakeWord::stop() {
367
+ if (this->state_ == STOPPED)
368
+ return;
344
369
 
345
- bool MicroWakeWord::generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]) {
346
- // Ensure we have enough new audio samples in the ring buffer for a full window
347
- if (!this->has_enough_samples_()) {
348
- return false;
349
- }
370
+ ESP_LOGD(TAG, "Stopping wake word detection");
350
371
 
351
- size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_),
352
- this->new_samples_to_get_() * sizeof(int16_t), pdMS_TO_TICKS(200));
372
+ this->pending_start_ = false;
373
+ this->pending_stop_ = true;
374
+ }
353
375
 
354
- if (bytes_read == 0) {
355
- ESP_LOGE(TAG, "Could not read data from Ring Buffer");
356
- } else if (bytes_read < this->new_samples_to_get_() * sizeof(int16_t)) {
357
- ESP_LOGD(TAG, "Partial Read of Data by Model");
358
- ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read,
359
- (int) (this->new_samples_to_get_() * sizeof(int16_t)));
360
- return false;
376
+ void MicroWakeWord::set_state_(State state) {
377
+ if (this->state_ != state) {
378
+ ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(micro_wake_word_state_to_string(this->state_)),
379
+ LOG_STR_ARG(micro_wake_word_state_to_string(state)));
380
+ this->state_ = state;
361
381
  }
382
+ }
362
383
 
363
- size_t num_samples_read;
364
- struct FrontendOutput frontend_output = FrontendProcessSamples(
365
- &this->frontend_state_, this->preprocessor_audio_buffer_, this->new_samples_to_get_(), &num_samples_read);
384
+ size_t MicroWakeWord::generate_features_(int16_t *audio_buffer, size_t samples_available,
385
+ int8_t features_buffer[PREPROCESSOR_FEATURE_SIZE]) {
386
+ size_t processed_samples = 0;
387
+ struct FrontendOutput frontend_output =
388
+ FrontendProcessSamples(&this->frontend_state_, audio_buffer, samples_available, &processed_samples);
366
389
 
367
390
  for (size_t i = 0; i < frontend_output.size; ++i) {
368
391
  // These scaling values are set to match the TFLite audio frontend int8 output.
@@ -372,8 +395,8 @@ bool MicroWakeWord::generate_features_for_window_(int8_t features[PREPROCESSOR_F
372
395
  // for historical reasons, to match up with the output of other feature
373
396
  // generators.
374
397
  // The process is then further complicated when we quantize the model. This
375
- // means we have to scale the 0.0 to 26.0 real values to the -128 to 127
376
- // signed integer numbers.
398
+ // means we have to scale the 0.0 to 26.0 real values to the -128 (INT8_MIN)
399
+ // to 127 (INT8_MAX) signed integer numbers.
377
400
  // All this means that to get matching values from our integer feature
378
401
  // output into the tensor input, we have to perform:
379
402
  // input = (((feature / 25.6) / 26.0) * 256) - 128
@@ -382,74 +405,63 @@ bool MicroWakeWord::generate_features_for_window_(int8_t features[PREPROCESSOR_F
382
405
  constexpr int32_t value_scale = 256;
383
406
  constexpr int32_t value_div = 666; // 666 = 25.6 * 26.0 after rounding
384
407
  int32_t value = ((frontend_output.values[i] * value_scale) + (value_div / 2)) / value_div;
385
- value -= 128;
386
- if (value < -128) {
387
- value = -128;
388
- }
389
- if (value > 127) {
390
- value = 127;
391
- }
392
- features[i] = value;
408
+
409
+ value += INT8_MIN; // Adds a -128; i.e., subtracts 128
410
+ features_buffer[i] = static_cast<int8_t>(clamp<int32_t>(value, INT8_MIN, INT8_MAX));
393
411
  }
394
412
 
395
- return true;
413
+ return processed_samples;
414
+ }
415
+
416
+ void MicroWakeWord::process_probabilities_() {
417
+ #ifdef USE_MICRO_WAKE_WORD_VAD
418
+ DetectionEvent vad_state = this->vad_model_->determine_detected();
419
+
420
+ this->vad_state_ = vad_state.detected; // atomic write, so thread safe
421
+ #endif
422
+
423
+ for (auto &model : this->wake_word_models_) {
424
+ if (model->get_unprocessed_probability_status()) {
425
+ // Only detect wake words if there is a new probability since the last check
426
+ DetectionEvent wake_word_state = model->determine_detected();
427
+ if (wake_word_state.detected) {
428
+ #ifdef USE_MICRO_WAKE_WORD_VAD
429
+ if (vad_state.detected) {
430
+ #endif
431
+ xQueueSend(this->detection_queue_, &wake_word_state, portMAX_DELAY);
432
+ model->reset_probabilities();
433
+ #ifdef USE_MICRO_WAKE_WORD_VAD
434
+ } else {
435
+ wake_word_state.blocked_by_vad = true;
436
+ xQueueSend(this->detection_queue_, &wake_word_state, portMAX_DELAY);
437
+ }
438
+ #endif
439
+ }
440
+ }
441
+ }
396
442
  }
397
443
 
398
- void MicroWakeWord::reset_states_() {
399
- ESP_LOGD(TAG, "Resetting buffers and probabilities");
400
- this->ring_buffer_->reset();
401
- this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION;
444
+ void MicroWakeWord::unload_models_() {
402
445
  for (auto &model : this->wake_word_models_) {
403
- model.reset_probabilities();
446
+ model->unload_model();
404
447
  }
405
448
  #ifdef USE_MICRO_WAKE_WORD_VAD
406
- this->vad_model_->reset_probabilities();
449
+ this->vad_model_->unload_model();
407
450
  #endif
408
451
  }
409
452
 
410
- bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver) {
411
- if (op_resolver.AddCallOnce() != kTfLiteOk)
412
- return false;
413
- if (op_resolver.AddVarHandle() != kTfLiteOk)
414
- return false;
415
- if (op_resolver.AddReshape() != kTfLiteOk)
416
- return false;
417
- if (op_resolver.AddReadVariable() != kTfLiteOk)
418
- return false;
419
- if (op_resolver.AddStridedSlice() != kTfLiteOk)
420
- return false;
421
- if (op_resolver.AddConcatenation() != kTfLiteOk)
422
- return false;
423
- if (op_resolver.AddAssignVariable() != kTfLiteOk)
424
- return false;
425
- if (op_resolver.AddConv2D() != kTfLiteOk)
426
- return false;
427
- if (op_resolver.AddMul() != kTfLiteOk)
428
- return false;
429
- if (op_resolver.AddAdd() != kTfLiteOk)
430
- return false;
431
- if (op_resolver.AddMean() != kTfLiteOk)
432
- return false;
433
- if (op_resolver.AddFullyConnected() != kTfLiteOk)
434
- return false;
435
- if (op_resolver.AddLogistic() != kTfLiteOk)
436
- return false;
437
- if (op_resolver.AddQuantize() != kTfLiteOk)
438
- return false;
439
- if (op_resolver.AddDepthwiseConv2D() != kTfLiteOk)
440
- return false;
441
- if (op_resolver.AddAveragePool2D() != kTfLiteOk)
442
- return false;
443
- if (op_resolver.AddMaxPool2D() != kTfLiteOk)
444
- return false;
445
- if (op_resolver.AddPad() != kTfLiteOk)
446
- return false;
447
- if (op_resolver.AddPack() != kTfLiteOk)
448
- return false;
449
- if (op_resolver.AddSplitV() != kTfLiteOk)
450
- return false;
451
-
452
- return true;
453
+ bool MicroWakeWord::update_model_probabilities_(const int8_t audio_features[PREPROCESSOR_FEATURE_SIZE]) {
454
+ bool success = true;
455
+
456
+ for (auto &model : this->wake_word_models_) {
457
+ // Perform inference
458
+ success = success & model->perform_streaming_inference(audio_features);
459
+ }
460
+ #ifdef USE_MICRO_WAKE_WORD_VAD
461
+ success = success & this->vad_model_->perform_streaming_inference(audio_features);
462
+ #endif
463
+
464
+ return success;
453
465
  }
454
466
 
455
467
  } // namespace micro_wake_word