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.
- esphome/__main__.py +16 -14
- esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
- esphome/components/adc/__init__.py +51 -34
- esphome/components/airthings_wave_base/__init__.py +1 -1
- esphome/components/alarm_control_panel/__init__.py +37 -2
- esphome/components/am43/cover/__init__.py +4 -5
- esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
- esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
- esphome/components/analog_threshold/binary_sensor.py +10 -8
- esphome/components/anova/climate.py +4 -5
- esphome/components/api/__init__.py +25 -8
- esphome/components/api/api_connection.cpp +81 -13
- esphome/components/api/api_connection.h +13 -1
- esphome/components/api/api_frame_helper.cpp +232 -177
- esphome/components/api/api_frame_helper.h +61 -8
- esphome/components/api/api_noise_context.h +13 -4
- esphome/components/api/api_pb2.cpp +1422 -1
- esphome/components/api/api_pb2.h +255 -1
- esphome/components/api/api_pb2_service.cpp +162 -49
- esphome/components/api/api_pb2_service.h +90 -51
- esphome/components/api/api_pb2_size.h +361 -0
- esphome/components/api/api_server.cpp +110 -34
- esphome/components/api/api_server.h +8 -0
- esphome/components/api/proto.h +86 -17
- esphome/components/as7341/as7341.h +1 -1
- esphome/components/atm90e32/__init__.py +1 -0
- esphome/components/atm90e32/atm90e32.cpp +576 -199
- esphome/components/atm90e32/atm90e32.h +128 -31
- esphome/components/atm90e32/atm90e32_reg.h +4 -2
- esphome/components/atm90e32/button/__init__.py +62 -10
- esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
- esphome/components/atm90e32/button/atm90e32_button.h +36 -4
- esphome/components/atm90e32/number/__init__.py +130 -0
- esphome/components/atm90e32/number/atm90e32_number.h +16 -0
- esphome/components/atm90e32/sensor.py +21 -4
- esphome/components/atm90e32/text_sensor/__init__.py +48 -0
- esphome/components/audio/__init__.py +96 -49
- esphome/components/audio/audio.h +48 -0
- esphome/components/audio/audio_decoder.cpp +1 -1
- esphome/components/audio/audio_resampler.cpp +2 -0
- esphome/components/audio/audio_resampler.h +1 -0
- esphome/components/ballu/climate.py +2 -9
- esphome/components/bang_bang/climate.py +5 -6
- esphome/components/bedjet/bedjet_hub.cpp +1 -0
- esphome/components/bedjet/climate/__init__.py +3 -8
- esphome/components/bedjet/fan/__init__.py +2 -11
- esphome/components/binary/fan/__init__.py +13 -16
- esphome/components/binary_sensor/__init__.py +13 -10
- esphome/components/bl0906/constants.h +16 -16
- esphome/components/ble_client/text_sensor/__init__.py +3 -5
- esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
- esphome/components/button/__init__.py +11 -8
- esphome/components/canbus/canbus.cpp +3 -0
- esphome/components/canbus/canbus.h +16 -0
- esphome/components/ccs811/sensor.py +9 -6
- esphome/components/climate/__init__.py +35 -2
- esphome/components/climate/climate_mode.h +1 -1
- esphome/components/climate/climate_traits.h +63 -57
- esphome/components/climate_ir/__init__.py +57 -17
- esphome/components/climate_ir_lg/climate.py +2 -5
- esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
- esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
- esphome/components/color/__init__.py +2 -0
- esphome/components/const/__init__.py +5 -0
- esphome/components/coolix/climate.py +2 -9
- esphome/components/copy/cover/__init__.py +10 -9
- esphome/components/copy/fan/__init__.py +11 -9
- esphome/components/copy/lock/__init__.py +11 -9
- esphome/components/copy/text/__init__.py +9 -6
- esphome/components/cover/__init__.py +37 -2
- esphome/components/cse7766/cse7766.cpp +2 -1
- esphome/components/cst226/binary_sensor/__init__.py +28 -0
- esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
- esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
- esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
- esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
- esphome/components/current_based/cover.py +37 -36
- esphome/components/current_based/current_based_cover.cpp +2 -1
- esphome/components/daikin/climate.py +2 -9
- esphome/components/daikin/daikin.cpp +15 -9
- esphome/components/daikin/daikin.h +5 -5
- esphome/components/daikin_arc/climate.py +2 -7
- esphome/components/daikin_brc/climate.py +3 -5
- esphome/components/dallas_temp/dallas_temp.cpp +17 -24
- esphome/components/dallas_temp/dallas_temp.h +0 -1
- esphome/components/daly_bms/daly_bms.cpp +2 -1
- esphome/components/debug/debug_component.cpp +6 -1
- esphome/components/debug/debug_component.h +6 -0
- esphome/components/debug/debug_esp32.cpp +109 -254
- esphome/components/debug/sensor.py +14 -0
- esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
- esphome/components/delonghi/climate.py +2 -9
- esphome/components/demo/__init__.py +18 -20
- esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
- esphome/components/dps310/sensor.py +6 -6
- esphome/components/ee895/sensor.py +9 -9
- esphome/components/emmeti/climate.py +2 -9
- esphome/components/endstop/cover.py +17 -16
- esphome/components/endstop/endstop_cover.cpp +2 -1
- esphome/components/ens160_base/__init__.py +12 -9
- esphome/components/esp32/__init__.py +60 -3
- esphome/components/esp32/core.cpp +11 -5
- esphome/components/esp32/gpio.cpp +86 -24
- esphome/components/esp32/gpio.py +15 -16
- esphome/components/esp32/gpio_esp32.py +1 -2
- esphome/components/esp32/gpio_esp32_c2.py +1 -1
- esphome/components/esp32/gpio_esp32_c3.py +1 -1
- esphome/components/esp32/gpio_esp32_c6.py +1 -1
- esphome/components/esp32/gpio_esp32_h2.py +1 -1
- esphome/components/esp32_ble/ble.cpp +1 -0
- esphome/components/esp32_ble/ble.h +5 -3
- esphome/components/esp32_ble/ble_advertising.cpp +2 -1
- esphome/components/esp32_ble/ble_advertising.h +1 -0
- esphome/components/esp32_ble_server/__init__.py +3 -0
- esphome/components/esp32_ble_tracker/__init__.py +7 -1
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
- esphome/components/esp32_camera/esp32_camera.cpp +2 -1
- esphome/components/esp32_camera/esp32_camera.h +1 -1
- esphome/components/esp32_can/esp32_can.cpp +1 -1
- esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
- esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
- esphome/components/esp32_rmt_led_strip/light.py +9 -1
- esphome/components/esp32_touch/esp32_touch.cpp +1 -1
- esphome/components/esp8266/gpio.cpp +69 -8
- esphome/components/ethernet/ethernet_component.cpp +1 -1
- esphome/components/event/__init__.py +13 -10
- esphome/components/factory_reset/switch/__init__.py +7 -21
- esphome/components/fan/__init__.py +52 -5
- esphome/components/fastled_base/__init__.py +1 -4
- esphome/components/fastled_base/fastled_light.cpp +1 -1
- esphome/components/feedback/cover.py +38 -33
- esphome/components/feedback/feedback_cover.cpp +2 -1
- esphome/components/fujitsu_general/climate.py +2 -9
- esphome/components/gcja5/gcja5.cpp +2 -1
- esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
- esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
- esphome/components/gpio_expander/cached_gpio.h +22 -7
- esphome/components/gps/__init__.py +47 -17
- esphome/components/gps/gps.cpp +42 -23
- esphome/components/gps/gps.h +17 -13
- esphome/components/graph/__init__.py +1 -2
- esphome/components/gree/climate.py +4 -6
- esphome/components/gree/gree.cpp +16 -2
- esphome/components/gree/gree.h +2 -2
- esphome/components/growatt_solar/growatt_solar.cpp +2 -1
- esphome/components/haier/climate.py +37 -34
- esphome/components/hbridge/fan/__init__.py +19 -17
- esphome/components/he60r/cover.py +4 -5
- esphome/components/heatpumpir/climate.py +3 -6
- esphome/components/hitachi_ac344/climate.py +2 -9
- esphome/components/hitachi_ac424/climate.py +2 -9
- esphome/components/hm3301/hm3301.h +1 -1
- esphome/components/hte501/sensor.py +6 -6
- esphome/components/http_request/__init__.py +39 -6
- esphome/components/http_request/http_request.cpp +20 -0
- esphome/components/http_request/http_request.h +57 -15
- esphome/components/http_request/http_request_arduino.cpp +22 -6
- esphome/components/http_request/http_request_arduino.h +4 -3
- esphome/components/http_request/http_request_host.cpp +141 -0
- esphome/components/http_request/http_request_host.h +37 -0
- esphome/components/http_request/http_request_idf.cpp +35 -3
- esphome/components/http_request/http_request_idf.h +10 -3
- esphome/components/http_request/httplib.h +9691 -0
- esphome/components/http_request/update/__init__.py +11 -8
- esphome/components/hyt271/sensor.py +6 -6
- esphome/components/i2c/i2c.h +4 -0
- esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
- esphome/components/i2s_audio/__init__.py +131 -22
- esphome/components/i2s_audio/i2s_audio.h +44 -4
- esphome/components/i2s_audio/media_player/__init__.py +19 -9
- esphome/components/i2s_audio/microphone/__init__.py +63 -5
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
- esphome/components/i2s_audio/speaker/__init__.py +31 -5
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
- esphome/components/ili9xxx/ili9xxx_init.h +1 -1
- esphome/components/image/__init__.py +37 -17
- esphome/components/image/image.cpp +25 -8
- esphome/components/internal_temperature/internal_temperature.cpp +6 -4
- esphome/components/key_collector/__init__.py +35 -0
- esphome/components/key_collector/key_collector.cpp +8 -0
- esphome/components/key_collector/key_collector.h +10 -0
- esphome/components/kuntze/kuntze.cpp +2 -1
- esphome/components/ld2410/ld2410.h +1 -1
- esphome/components/ld2450/ld2450.h +1 -1
- esphome/components/light/__init__.py +57 -0
- esphome/components/lock/__init__.py +51 -4
- esphome/components/lock/automation.h +2 -13
- esphome/components/logger/__init__.py +22 -0
- esphome/components/logger/logger.cpp +154 -103
- esphome/components/logger/logger.h +211 -36
- esphome/components/logger/task_log_buffer.cpp +138 -0
- esphome/components/logger/task_log_buffer.h +69 -0
- esphome/components/lvgl/__init__.py +13 -5
- esphome/components/lvgl/automation.py +50 -1
- esphome/components/lvgl/defines.py +0 -1
- esphome/components/lvgl/lvgl_esphome.cpp +5 -1
- esphome/components/lvgl/text/__init__.py +1 -2
- esphome/components/mapping/__init__.py +134 -0
- esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
- esphome/components/max7219digit/max7219digit.cpp +28 -27
- esphome/components/mdns/__init__.py +11 -5
- esphome/components/mdns/mdns_component.cpp +11 -5
- esphome/components/mdns/mdns_component.h +3 -2
- esphome/components/mdns/mdns_esp32.cpp +4 -3
- esphome/components/mdns/mdns_esp8266.cpp +4 -2
- esphome/components/mdns/mdns_libretiny.cpp +4 -2
- esphome/components/mdns/mdns_rp2040.cpp +4 -2
- esphome/components/media_player/__init__.py +33 -1
- esphome/components/mhz19/sensor.py +11 -7
- esphome/components/micro_wake_word/__init__.py +99 -31
- esphome/components/micro_wake_word/automation.h +54 -0
- esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
- esphome/components/micro_wake_word/micro_wake_word.h +58 -105
- esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
- esphome/components/micro_wake_word/streaming_model.cpp +158 -41
- esphome/components/micro_wake_word/streaming_model.h +85 -13
- esphome/components/microphone/__init__.py +139 -9
- esphome/components/microphone/automation.h +14 -2
- esphome/components/microphone/microphone.cpp +21 -0
- esphome/components/microphone/microphone.h +14 -5
- esphome/components/microphone/microphone_source.cpp +95 -0
- esphome/components/microphone/microphone_source.h +80 -0
- esphome/components/mics_4514/sensor.py +25 -14
- esphome/components/midea/climate.py +3 -4
- esphome/components/midea_ir/climate.py +3 -5
- esphome/components/mipi_spi/__init__.py +15 -0
- esphome/components/mipi_spi/display.py +474 -0
- esphome/components/mipi_spi/mipi_spi.cpp +481 -0
- esphome/components/mipi_spi/mipi_spi.h +171 -0
- esphome/components/mipi_spi/models/__init__.py +65 -0
- esphome/components/mipi_spi/models/amoled.py +72 -0
- esphome/components/mipi_spi/models/commands.py +82 -0
- esphome/components/mipi_spi/models/cyd.py +10 -0
- esphome/components/mipi_spi/models/ili.py +749 -0
- esphome/components/mipi_spi/models/jc.py +260 -0
- esphome/components/mipi_spi/models/lanbon.py +15 -0
- esphome/components/mipi_spi/models/lilygo.py +60 -0
- esphome/components/mipi_spi/models/waveshare.py +139 -0
- esphome/components/mitsubishi/climate.py +2 -5
- esphome/components/mitsubishi/mitsubishi.cpp +9 -9
- esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
- esphome/components/mixer/speaker/mixer_speaker.h +1 -3
- esphome/components/mlx90393/sensor.py +5 -0
- esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
- esphome/components/mlx90393/sensor_mlx90393.h +21 -4
- esphome/components/modbus/modbus.cpp +2 -1
- esphome/components/mqtt/__init__.py +1 -1
- esphome/components/mqtt/mqtt_client.cpp +6 -2
- esphome/components/mqtt/mqtt_const.h +4 -0
- esphome/components/mqtt/mqtt_fan.cpp +39 -0
- esphome/components/mqtt/mqtt_fan.h +2 -0
- esphome/components/ms5611/sensor.py +6 -6
- esphome/components/ms8607/sensor.py +3 -3
- esphome/components/network/__init__.py +1 -1
- esphome/components/nextion/base_component.py +17 -16
- esphome/components/nextion/display.py +11 -2
- esphome/components/nextion/nextion.cpp +39 -1
- esphome/components/nextion/nextion.h +50 -0
- esphome/components/noblex/climate.py +2 -9
- esphome/components/number/__init__.py +12 -9
- esphome/components/one_wire/one_wire_bus.cpp +14 -10
- esphome/components/one_wire/one_wire_bus.h +14 -8
- esphome/components/online_image/bmp_image.cpp +48 -11
- esphome/components/online_image/bmp_image.h +2 -0
- esphome/components/opentherm/binary_sensor/__init__.py +2 -4
- esphome/components/opentherm/number/__init__.py +11 -20
- esphome/components/opentherm/sensor/__init__.py +3 -3
- esphome/components/opentherm/switch/__init__.py +3 -5
- esphome/components/output/lock/__init__.py +11 -9
- esphome/components/packages/__init__.py +33 -31
- esphome/components/packet_transport/__init__.py +201 -0
- esphome/components/packet_transport/binary_sensor.py +19 -0
- esphome/components/packet_transport/packet_transport.cpp +534 -0
- esphome/components/packet_transport/packet_transport.h +154 -0
- esphome/components/packet_transport/sensor.py +19 -0
- esphome/components/pca9685/pca9685_output.cpp +2 -1
- esphome/components/pid/climate.py +2 -4
- esphome/components/pm2005/__init__.py +1 -0
- esphome/components/pm2005/pm2005.cpp +123 -0
- esphome/components/pm2005/pm2005.h +46 -0
- esphome/components/pm2005/sensor.py +86 -0
- esphome/components/pmsa003i/pmsa003i.cpp +43 -16
- esphome/components/pmsa003i/pmsa003i.h +25 -25
- esphome/components/pmsx003/pmsx003.cpp +195 -230
- esphome/components/pmsx003/pmsx003.h +51 -33
- esphome/components/pmsx003/sensor.py +21 -11
- esphome/components/pn7150/pn7150.h +2 -2
- esphome/components/pn7160/pn7160.h +2 -2
- esphome/components/prometheus/prometheus_handler.cpp +174 -0
- esphome/components/prometheus/prometheus_handler.h +17 -0
- esphome/components/psram/__init__.py +7 -5
- esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
- esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
- esphome/components/pzem004t/pzem004t.cpp +2 -1
- esphome/components/qspi_dbi/__init__.py +0 -1
- esphome/components/qspi_dbi/display.py +2 -1
- esphome/components/qspi_dbi/models.py +1 -2
- esphome/components/remote_base/__init__.py +91 -0
- esphome/components/remote_base/beo4_protocol.cpp +153 -0
- esphome/components/remote_base/beo4_protocol.h +43 -0
- esphome/components/remote_base/gobox_protocol.cpp +131 -0
- esphome/components/remote_base/gobox_protocol.h +54 -0
- esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
- esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
- esphome/components/resampler/speaker/resampler_speaker.h +1 -1
- esphome/components/rf_bridge/rf_bridge.cpp +2 -1
- esphome/components/scd30/sensor.py +2 -3
- esphome/components/scd4x/sensor.py +4 -5
- esphome/components/sdp3x/sensor.py +2 -1
- esphome/components/sds011/sds011.cpp +2 -1
- esphome/components/select/__init__.py +19 -20
- esphome/components/sen5x/sen5x.cpp +55 -36
- esphome/components/sen5x/sensor.py +1 -1
- esphome/components/senseair/sensor.py +3 -3
- esphome/components/sensor/__init__.py +158 -14
- esphome/components/sensor/filter.cpp +23 -0
- esphome/components/sensor/filter.h +22 -0
- esphome/components/sgp30/sensor.py +14 -16
- esphome/components/sgp4x/sensor.py +1 -1
- esphome/components/sht4x/sht4x.cpp +43 -22
- esphome/components/sht4x/sht4x.h +1 -1
- esphome/components/shtcx/sensor.py +6 -6
- esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
- esphome/components/sml/text_sensor/__init__.py +4 -6
- esphome/components/sound_level/__init__.py +0 -0
- esphome/components/sound_level/sensor.py +97 -0
- esphome/components/sound_level/sound_level.cpp +194 -0
- esphome/components/sound_level/sound_level.h +73 -0
- esphome/components/speaker/media_player/__init__.py +4 -8
- esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
- esphome/components/speaker/media_player/speaker_media_player.h +0 -11
- esphome/components/speaker/speaker.h +4 -7
- esphome/components/speed/fan/__init__.py +17 -16
- esphome/components/spi/spi.h +11 -1
- esphome/components/sprinkler/__init__.py +18 -19
- esphome/components/sprinkler/sprinkler.cpp +6 -5
- esphome/components/switch/__init__.py +32 -42
- esphome/components/syslog/__init__.py +41 -0
- esphome/components/syslog/esphome_syslog.cpp +49 -0
- esphome/components/syslog/esphome_syslog.h +27 -0
- esphome/components/t6615/sensor.py +3 -3
- esphome/components/t6615/t6615.cpp +2 -1
- esphome/components/tca9555/tca9555.cpp +11 -6
- esphome/components/tcl112/climate.py +2 -9
- esphome/components/template/alarm_control_panel/__init__.py +7 -6
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
- esphome/components/template/cover/__init__.py +27 -21
- esphome/components/template/fan/__init__.py +14 -12
- esphome/components/template/lock/__init__.py +20 -25
- esphome/components/template/lock/automation.h +18 -0
- esphome/components/template/text/__init__.py +4 -3
- esphome/components/template/valve/__init__.py +32 -21
- esphome/components/template/valve/automation.h +24 -0
- esphome/components/text/__init__.py +32 -1
- esphome/components/text_sensor/__init__.py +24 -29
- esphome/components/thermostat/climate.py +5 -5
- esphome/components/time_based/cover.py +17 -16
- esphome/components/time_based/time_based_cover.cpp +2 -1
- esphome/components/tm1638/switch/__init__.py +10 -7
- esphome/components/tormatic/cover.py +4 -5
- esphome/components/toshiba/climate.py +3 -5
- esphome/components/touchscreen/touchscreen.cpp +3 -1
- esphome/components/tuya/climate/__init__.py +5 -6
- esphome/components/tuya/cover/__init__.py +6 -11
- esphome/components/tuya/select/__init__.py +15 -5
- esphome/components/tuya/select/tuya_select.cpp +6 -1
- esphome/components/tuya/select/tuya_select.h +5 -1
- esphome/components/uart/packet_transport/__init__.py +20 -0
- esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
- esphome/components/uart/packet_transport/uart_transport.h +41 -0
- esphome/components/uart/switch/uart_switch.cpp +2 -1
- esphome/components/udp/__init__.py +126 -128
- esphome/components/udp/automation.h +40 -0
- esphome/components/udp/binary_sensor.py +3 -25
- esphome/components/udp/packet_transport/__init__.py +29 -0
- esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
- esphome/components/udp/packet_transport/udp_transport.h +28 -0
- esphome/components/udp/sensor.py +3 -25
- esphome/components/udp/udp_component.cpp +26 -470
- esphome/components/udp/udp_component.h +21 -128
- esphome/components/update/__init__.py +31 -1
- esphome/components/uponor_smatrix/climate/__init__.py +4 -9
- esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
- esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
- esphome/components/uptime/text_sensor/__init__.py +47 -7
- esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
- esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
- esphome/components/valve/__init__.py +34 -3
- esphome/components/valve/automation.h +1 -19
- esphome/components/vl53l0x/sensor.py +11 -0
- esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
- esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
- esphome/components/voice_assistant/__init__.py +36 -10
- esphome/components/voice_assistant/voice_assistant.cpp +170 -144
- esphome/components/voice_assistant/voice_assistant.h +26 -25
- esphome/components/waveshare_epaper/display.py +6 -0
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
- esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
- esphome/components/whirlpool/climate.py +3 -5
- esphome/components/whynter/climate.py +3 -5
- esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
- esphome/components/yashima/climate.py +6 -6
- esphome/components/zhlt01/climate.py +2 -7
- esphome/config.py +13 -13
- esphome/config_validation.py +38 -58
- esphome/const.py +15 -1
- esphome/core/__init__.py +2 -0
- esphome/core/application.cpp +23 -10
- esphome/core/application.h +9 -1
- esphome/core/automation.h +4 -3
- esphome/core/component.cpp +28 -7
- esphome/core/component.h +10 -1
- esphome/core/defines.h +23 -17
- esphome/core/macros.h +4 -0
- esphome/core/scheduler.cpp +7 -1
- esphome/cpp_generator.py +6 -2
- esphome/dashboard/web_server.py +3 -3
- esphome/helpers.py +39 -0
- esphome/loader.py +4 -0
- esphome/log.py +15 -19
- esphome/mqtt.py +23 -10
- esphome/platformio_api.py +1 -1
- esphome/schema_extractors.py +0 -1
- esphome/voluptuous_schema.py +3 -1
- esphome/vscode.py +15 -0
- esphome/wizard.py +47 -37
- esphome/zeroconf.py +7 -3
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/METADATA +10 -11
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/RECORD +440 -380
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/WHEEL +1 -1
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/entry_points.txt +0 -0
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.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
|
11
|
-
#include <frontend_util.h>
|
9
|
+
#include "esphome/components/audio/audio_transfer_buffer.h"
|
12
10
|
|
13
|
-
#
|
14
|
-
#include
|
15
|
-
#
|
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
|
25
|
-
|
26
|
-
static const size_t
|
27
|
-
|
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::
|
34
|
-
return LOG_STR("
|
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::
|
42
|
-
return LOG_STR("
|
43
|
-
case State::
|
44
|
-
return LOG_STR("
|
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
|
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
|
-
|
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
|
-
|
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->
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
96
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
194
|
+
}
|
146
195
|
}
|
147
|
-
}
|
148
196
|
|
149
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
}
|
199
|
+
this_mww->unload_models_();
|
200
|
+
this_mww->microphone_source_->stop();
|
201
|
+
FrontendFreeStateContents(&this_mww->frontend_state_);
|
159
202
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
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
|
-
|
178
|
-
|
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::
|
182
|
-
if (this->
|
183
|
-
|
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::
|
194
|
-
|
195
|
-
|
196
|
-
|
235
|
+
void MicroWakeWord::resume_task_() {
|
236
|
+
if (this->inference_task_handle_ != nullptr) {
|
237
|
+
vTaskResume(this->inference_task_handle_);
|
238
|
+
}
|
197
239
|
}
|
198
240
|
|
199
|
-
|
200
|
-
|
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
|
-
|
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 (
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
220
|
-
|
265
|
+
if (event_group_bits & EventGroupBits::TASK_RUNNING) {
|
266
|
+
ESP_LOGD(TAG, "Inference task is running");
|
221
267
|
|
222
|
-
|
223
|
-
this->
|
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 (
|
231
|
-
|
232
|
-
|
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 (
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
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
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
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
|
-
|
283
|
-
|
308
|
+
xTaskCreate(MicroWakeWord::inference_task, "mww", INFERENCE_TASK_STACK_SIZE, (void *) this,
|
309
|
+
INFERENCE_TASK_PRIORITY, &this->inference_task_handle_);
|
284
310
|
|
285
|
-
|
286
|
-
|
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::
|
294
|
-
|
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
|
-
|
301
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
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
|
-
|
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
|
-
|
362
|
+
this->pending_start_ = true;
|
363
|
+
this->pending_stop_ = false;
|
338
364
|
}
|
339
365
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
}
|
366
|
+
void MicroWakeWord::stop() {
|
367
|
+
if (this->state_ == STOPPED)
|
368
|
+
return;
|
344
369
|
|
345
|
-
|
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
|
-
|
352
|
-
|
372
|
+
this->pending_start_ = false;
|
373
|
+
this->pending_stop_ = true;
|
374
|
+
}
|
353
375
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
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
|
-
|
364
|
-
|
365
|
-
|
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
|
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
|
-
|
386
|
-
|
387
|
-
|
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
|
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::
|
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
|
446
|
+
model->unload_model();
|
404
447
|
}
|
405
448
|
#ifdef USE_MICRO_WAKE_WORD_VAD
|
406
|
-
this->vad_model_->
|
449
|
+
this->vad_model_->unload_model();
|
407
450
|
#endif
|
408
451
|
}
|
409
452
|
|
410
|
-
bool MicroWakeWord::
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
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
|