M5stack M5CoreS3 SE with MicroWakeWord

Got microwakework working on the M5CoreS3 SE. Speaker output is garbage, microphone works pretty good. Might be helpful to anyone that already has one but wouldn’t recommend it at this point. Especially with the Seeed respeaker lite announcement and Nabu announcing that there voice assistant will have a XIOS chip in it like the respeaker lite. I had timers and text on the screen working but it kept locking up or stopped responding so I removed it. I also didn’t install ESP-ADF but the tensorflow/lite part still runs. Guess it’s not required? Maybe someone could clear that up for me. I saw the respeaker lite’s yaml didn’t have it either but I assumed that is because it’s using the XIOS chip for obvious reasons.

substitutions:
  name: m5cores3
  friendly_name: M5CoreS3
  loading_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/loading_320_240.png
  idle_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/idle_320_240.png
  listening_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/listening_320_240.png
  thinking_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/thinking_320_240.png
  replying_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/replying_320_240.png
  error_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/error_320_240.png

  loading_illustration_background_color: '000000'
  idle_illustration_background_color: '000000'
  listening_illustration_background_color: 'FFFFFF'
  thinking_illustration_background_color: 'FFFFFF'
  replying_illustration_background_color: 'FFFFFF'
  error_illustration_background_color: '000000'

  voice_assist_idle_phase_id: '1'
  voice_assist_listening_phase_id: '2'
  voice_assist_thinking_phase_id: '3'
  voice_assist_replying_phase_id: '4'
  voice_assist_not_ready_phase_id: '10'
  voice_assist_error_phase_id: '11'  
  voice_assist_muted_phase_id: '12'

  micro_wake_word_model: hey_jarvis  

esphome:
  name: m5cores3
  friendly_name: M5CoreS3
  platformio_options:
    board_build.flash_mode: dio
    #board_upload.maximum_size: 16777216
    #board_build.f_cpu : 240000000L    
  libraries:
    - m5stack/M5GFX@^0.1.11
    - m5stack/M5Unified@^0.1.11
  on_boot:
    priority: 600
    then:
      - delay: 30s            
      - if:
          condition:
            - lambda: return id(init_in_progress);
          then:
            - lambda: id(init_in_progress) = false;      
      - script.execute: draw_display  # Initialize the display
      - delay: 1s  # Wait a moment to ensure the display is ready
      - switch.turn_on: switch_lcd_backlight  # Turn on the backlight switch to set the initial state

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      # need to set a s3 compatible board for the adf-sdk to compile
      # board specific code is not used though
      CONFIG_ESP32_S3_BOX_BOARD: "y"
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB:      "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B:  "y"

      #CONFIG_LOG_DEFAULT_LEVEL_DEBUG: "y"
      #CONFIG_LOG_DEFAULT_LEVEL: "4"

psram:
  mode: quad
  speed: 80MHz

external_components:
  - source:
      type: git
      url: https://github.com/m5stack/M5CoreS3-Esphome
    components: [ board_m5cores3, m5cores3_audio, m5cores3_display ]
    refresh: 0s
  #- source: github://pr#5230
  #  components: esp_adf
 #   refresh: 0s        
  #- source: github://jesserockz/esphome-components
  #  components: [file]
  #  refresh: 0s
text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP Address"
time:
  - platform: homeassistant
    id: homeassistant_time
# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "APIKEY"
  on_client_connected:
    - script.execute: draw_display
  on_client_disconnected:
    - script.execute: draw_display

ota:
  - platform: esphome
    password: "OTAPW"

wifi:
  ssid: !secret wifi_ssid 
  password: !secret wifi_password
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "M5Stack-Cores3 Fallback Hotspot"
    password: "HOTSTPOTPW"
  on_connect:
    - script.execute: draw_display
   # - delay: 5s # Gives time for improv results to be transmitted 
  on_disconnect:
    - script.execute: draw_display   



captive_portal:

# 
# 
# Globals
# 
globals:
  - id: init_in_progress
    type: bool
    restore_value: no
    initial_value: 'true'
  - id: voice_assistant_phase
    type: int
    restore_value: no
    initial_value: ${voice_assist_not_ready_phase_id}

#
# Temp values for slider lcd brightnes
#
number:
  - platform: template
    name: "Display Brightness"
    id: display_brightness
    entity_category: config
    optimistic: true
    min_value: 0
    max_value: 200
    step: 1
    unit_of_measurement: "%"
    initial_value: 127
    icon: "mdi:brightness-6"
    set_action:
      - lambda: |-
          int brightness = (int)(x * 2.55);  
          M5.Display.setBrightness(brightness);
          

# 
# Display
# 
script:
  - id: draw_display
    then:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - if:
                condition:
                  wifi.connected:
                then:
                  - if:
                      condition:
                        api.connected:
                      then:
                        - lambda: |
                            switch(id(voice_assistant_phase)) {
                              case ${voice_assist_listening_phase_id}:
                                id(m5cores3_lcd).show_page(listening_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_thinking_phase_id}:
                                id(m5cores3_lcd).show_page(thinking_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_replying_phase_id}:
                                id(m5cores3_lcd).show_page(replying_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_error_phase_id}:
                                id(m5cores3_lcd).show_page(error_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_muted_phase_id}:
                                id(m5cores3_lcd).show_page(muted_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_not_ready_phase_id}:
                                id(m5cores3_lcd).show_page(no_ha_page);
                                id(m5cores3_lcd).update();
                                break;
                              default:
                                id(m5cores3_lcd).show_page(idle_page);
                                id(m5cores3_lcd).update();
                            }
                      else:
                        - display.page.show: no_ha_page
                        - component.update: m5cores3_lcd
                else:
                  - display.page.show: no_wifi_page
                  - component.update: m5cores3_lcd
          else:
            - display.page.show: initializing_page
            - component.update: m5cores3_lcd

image:
  - file: ${error_illustration_file}
    id: casita_error
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${idle_illustration_file}
    id: casita_idle
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${listening_illustration_file}
    id: casita_listening
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${thinking_illustration_file}
    id: casita_thinking
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${replying_illustration_file}
    id: casita_replying
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${loading_illustration_file}
    id: casita_initializing
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: https://github.com/esphome/firmware/raw/main/voice-assistant/error_box_illustrations/error-no-wifi.png
    id: error_no_wifi
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: https://github.com/esphome/firmware/raw/main/voice-assistant/error_box_illustrations/error-no-ha.png
    id: error_no_ha
    resize: 320x240
    type: RGB24
    use_transparency: true

color:
  - id: idle_color
    hex: ${idle_illustration_background_color}
  - id: listening_color
    hex: ${listening_illustration_background_color}
  - id: thinking_color
    hex: ${thinking_illustration_background_color}
  - id: replying_color
    hex: ${replying_illustration_background_color}
  - id: loading_color
    hex: ${loading_illustration_background_color}
  - id: error_color
    hex: ${error_illustration_background_color}

display:
  - platform: m5cores3_display
    model: ILI9342
    dc_pin: 35
    update_interval: never
    id: m5cores3_lcd
    pages:
      - id: idle_page
        lambda: |-
          it.fill(id(idle_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_idle), ImageAlign::CENTER);
      - id: listening_page
        lambda: |-
          it.fill(id(listening_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_listening), ImageAlign::CENTER);
      - id: thinking_page
        lambda: |-
          it.fill(id(thinking_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_thinking), ImageAlign::CENTER);
      - id: replying_page
        lambda: |-
          it.fill(id(replying_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_replying), ImageAlign::CENTER);
      - id: error_page
        lambda: |-
          it.fill(id(error_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_error), ImageAlign::CENTER);
      - id: no_ha_page
        lambda: |-
          it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_ha), ImageAlign::CENTER);
      - id: no_wifi_page
        lambda: |-
          it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_wifi), ImageAlign::CENTER);
      - id: initializing_page
        lambda: |-
          it.fill(id(loading_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_initializing), ImageAlign::CENTER);
      - id: muted_page
        lambda: |-
          it.fill(Color::BLACK);


# 
# Audio
# 
#esp_adf:
board_m5cores3:
m5cores3_audio:
  id: m5cores3_audio_1

microphone:
  - platform: m5cores3_audio
    m5cores3_audio_id: m5cores3_audio_1
    id: m5cores3_mic
    adc_type: external
    i2s_din_pin: 14
    pdm: false

speaker:
  - platform: m5cores3_audio
    m5cores3_audio_id: m5cores3_audio_1
    id: m5cores3_spk
    dac_type: external
    i2s_dout_pin: 13
    mode: mono

# 
# VA
# 
micro_wake_word:
  models: ${micro_wake_word_model}
  on_wake_word_detected:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;
        
voice_assistant:
  id: va
  microphone: m5cores3_mic
  speaker: m5cores3_spk
  use_wake_word: true
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  #vad_threshold: 3
  on_listening:
    - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
    - script.execute: draw_display
  on_stt_vad_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
    - script.execute: draw_display
  on_tts_stream_start:
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - script.execute: draw_display
  on_tts_stream_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
    - script.execute: draw_display
  on_error:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:
          - lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};  
          - script.execute: draw_display
          - delay: 1s
          - if:
              condition:
                switch.is_off: mute
              then:
                - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
              else:
                - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
          - script.execute: draw_display
  on_client_connected: 
    - if:
        condition:
          switch.is_off: mute
        then:
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false; 
    - script.execute: draw_display
  on_client_disconnected:
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};  
    - script.execute: draw_display

select:
  - platform: template
    entity_category: config
    name: Wake word engine location
    id: wake_word_engine_location
    optimistic: true
    restore_value: true
    options:
      - In Home Assistant
      - On device
    initial_option: On device
    on_value:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - wait_until:
                lambda: return id(voice_assistant_phase) == ${voice_assist_muted_phase_id} || id(voice_assistant_phase) == ${voice_assist_idle_phase_id};
            - if:
                condition:
                  lambda: return x == "In Home Assistant";
                then:
                  - micro_wake_word.stop
                  - delay: 30ms
                  - if:
                      condition:
                        switch.is_off: mute
                      then:
                        - lambda: id(va).set_use_wake_word(true);
                        - voice_assistant.start_continuous:
            - if:
                condition:
                  lambda: return x == "On device";
                then:
                  - lambda: id(va).set_use_wake_word(false);
                  - voice_assistant.stop
                  - delay: 30ms
                  - if:
                      condition:
                        switch.is_off: mute
                      then:
                        - micro_wake_word.start      
      


switch:
- platform: template
  name: "LCD Backlight"
  id: switch_lcd_backlight
  restore_mode: "ALWAYS_ON"
  turn_on_action:
    - lambda: |-
        int brightness = id(display_brightness).state; 
        M5.Display.setBrightness(brightness);          
        id(switch_lcd_backlight).publish_state(true);
  turn_off_action:
    - lambda: |-
        M5.Display.setBrightness(0);                  
        id(switch_lcd_backlight).publish_state(false);
- platform: template
  name: Mute
  id: mute
  optimistic: true
  restore_mode: RESTORE_DEFAULT_OFF
  entity_category: config
  on_turn_off:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:      
          - lambda: id(va).set_use_wake_word(true);
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
          - if:
              condition:
                not:
                  - voice_assistant.is_running
              then:
                - voice_assistant.start_continuous
          - script.execute: draw_display
  on_turn_on:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:      
          - voice_assistant.stop
          - lambda: id(va).set_use_wake_word(false);
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
          - script.execute: draw_display

@ginandbacon Thank you for sharing your ESPHome config for the CoreS3 SE. I’ve managed to get wake word working on my Atom Echo but so far had no luck with the CoreS3.

Flashing was successful, however should I expect to see something on the screen after I have installed this config? Looking at the console logs via USB I can see the device has connected to wifi, and I’ve added it to HA’s ESPHome Devices along with my encryption key, but so far the device looks dead externally.

I take it you added it to HA after adding to ESPHome, correct? It should reboot after flashing and show a black background with a face like the S3-Box, it will turn red if it can’t connect to HA, something else if it can’t connect to WiFi. I just updated mine to the latest non beta ESPHome version and it works. I was also trying to get alarms and text to speech to display on screen but it just kept acting way to “wonky”. so i may have left something in there that I should have removed. I do know the below works.

Does the device show as red even when it’s on and has an IP? I found out that if you addd the project and version yourself, it does this, I might have taken it out, just something I ran accress with this and my Korvo-1 which was odd until I figured it out as it all worked, it just showed as red in ESPHome add on

Try this, I was having some random issues myself, I think something at the beginning was out of order or causing issues. That or leaving captive_portal: in there when not using arduino for the platform (I am not a developer). The one odd random thing I had is it stopped doing OTA updates, I can plug it in to a PC, no issues, that’s why I commented out that lines at the beginning.

It should show something, that is unless it doesn’t have internet access. The beginning its’ downloading images. There is a way to do this without downloading them everytime, I think if you create an images folder in the esphome folder you could just reference the as loading_illustration_file: image/image.jpg (it’s on these forums somewhere)

ORA Error:

INFO Uploading /data/build/m5cores3/.pioenvs/m5cores3/firmware.bin (3502752 bytes)
ERROR Error receiving acknowledge binary size: timed out

substitutions:
  name: m5cores3
  friendly_name: M5CoreS3
  loading_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/loading_320_240.png
  idle_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/idle_320_240.png
  listening_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/listening_320_240.png
  thinking_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/thinking_320_240.png
  replying_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/replying_320_240.png
  error_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/error_320_240.png

  loading_illustration_background_color: '000000'
  idle_illustration_background_color: '000000'
  listening_illustration_background_color: 'FFFFFF'
  thinking_illustration_background_color: 'FFFFFF'
  replying_illustration_background_color: 'FFFFFF'
  error_illustration_background_color: '000000'

  voice_assist_idle_phase_id: '1'
  voice_assist_listening_phase_id: '2'
  voice_assist_thinking_phase_id: '3'
  voice_assist_replying_phase_id: '4'
  voice_assist_not_ready_phase_id: '10'
  voice_assist_error_phase_id: '11'  
  voice_assist_muted_phase_id: '12'

  micro_wake_word_model: hey_jarvis  

esphome:
  name: m5cores3
  friendly_name: M5CoreS3
  platformio_options:
    board_build.flash_mode: dio
    #board_upload.maximum_size: 16777216
    #board_build.f_cpu : 240000000L    
  libraries:
    - m5stack/M5GFX@^0.1.11
    - m5stack/M5Unified@^0.1.11
  on_boot:
    priority: 600
    then:
      - delay: 30s            
      - if:
          condition:
            - lambda: return id(init_in_progress);
          then:
            - lambda: id(init_in_progress) = false;      
      - script.execute: draw_display  # Initialize the display
      - delay: 1s  # Wait a moment to ensure the display is ready
      - switch.turn_on: switch_lcd_backlight  # Turn on the backlight switch to set the initial state

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      # need to set a s3 compatible board for the adf-sdk to compile
      # board specific code is not used though  
      CONFIG_ESP32_S3_BOX_BOARD: "y"
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB:      "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B:  "y"

      #CONFIG_LOG_DEFAULT_LEVEL_DEBUG: "y"
      #CONFIG_LOG_DEFAULT_LEVEL: "4"

psram:
  mode: quad
  speed: 80MHz

external_components:
  - source:
      type: git
      url: https://github.com/m5stack/M5CoreS3-Esphome
    components: [ board_m5cores3, m5cores3_audio, m5cores3_display ]
    refresh: 0s
  #- source: github://pr#5230
  #  components: esp_adf
 #   refresh: 0s        
  #- source: github://jesserockz/esphome-components
  #  components: [file]
  #  refresh: 0s


# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "API"
    
ota:
  - platform: esphome
    id: my_ota
    password: "OTA"

wifi:
  ssid: !secret wifi_ssid 
  password: !secret wifi_password
  on_connect:
    then:
      - delay: 20ms # Gives time for improv results to be transmitted
      - ble.disable:
      - script.execute: draw_display        
  on_disconnect:
    then:
      - ble.enable:  
      - script.execute: draw_display

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "M5Stack-Cores3 Fallback Hotspot"
    password: "HOTSPOT"

improv_serial:

esp32_improv:
  authorizer: none  
# 
# 
# Globals
# 
globals:
  - id: init_in_progress
    type: bool
    restore_value: no
    initial_value: 'true'
  - id: voice_assistant_phase
    type: int
    restore_value: no
    initial_value: ${voice_assist_not_ready_phase_id}

time:
  - platform: homeassistant
    id: homeassistant_time    

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP Address"    

#
# Temp values for slider lcd brightnes
#
number:
  - platform: template
    name: "Display Brightness"
    id: display_brightness
    entity_category: config
    optimistic: true
    min_value: 0
    max_value: 200
    step: 1
    unit_of_measurement: "%"
    initial_value: 127
    icon: "mdi:brightness-6"
    set_action:
      - lambda: |-
          int brightness = (int)(x * 2.55);  
          M5.Display.setBrightness(brightness);
          

# 
# Display
# 
script:
  - id: draw_display
    then:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - if:
                condition:
                  wifi.connected:
                then:
                  - if:
                      condition:
                        api.connected:
                      then:
                        - lambda: |
                            switch(id(voice_assistant_phase)) {
                              case ${voice_assist_listening_phase_id}:
                                id(m5cores3_lcd).show_page(listening_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_thinking_phase_id}:
                                id(m5cores3_lcd).show_page(thinking_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_replying_phase_id}:
                                id(m5cores3_lcd).show_page(replying_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_error_phase_id}:
                                id(m5cores3_lcd).show_page(error_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_muted_phase_id}:
                                id(m5cores3_lcd).show_page(muted_page);
                                id(m5cores3_lcd).update();
                                break;
                              case ${voice_assist_not_ready_phase_id}:
                                id(m5cores3_lcd).show_page(no_ha_page);
                                id(m5cores3_lcd).update();
                                break;
                              default:
                                id(m5cores3_lcd).show_page(idle_page);
                                id(m5cores3_lcd).update();
                            }
                      else:
                        - display.page.show: no_ha_page
                        - component.update: m5cores3_lcd
                else:
                  - display.page.show: no_wifi_page
                  - component.update: m5cores3_lcd
          else:
            - display.page.show: initializing_page
            - component.update: m5cores3_lcd

image:
  - file: ${error_illustration_file}
    id: casita_error
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${idle_illustration_file}
    id: casita_idle
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${listening_illustration_file}
    id: casita_listening
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${thinking_illustration_file}
    id: casita_thinking
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${replying_illustration_file}
    id: casita_replying
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: ${loading_illustration_file}
    id: casita_initializing
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: https://github.com/esphome/firmware/raw/main/voice-assistant/error_box_illustrations/error-no-wifi.png
    id: error_no_wifi
    resize: 320x240
    type: RGB24
    use_transparency: true
  - file: https://github.com/esphome/firmware/raw/main/voice-assistant/error_box_illustrations/error-no-ha.png
    id: error_no_ha
    resize: 320x240
    type: RGB24
    use_transparency: true

color:
  - id: idle_color
    hex: ${idle_illustration_background_color}
  - id: listening_color
    hex: ${listening_illustration_background_color}
  - id: thinking_color
    hex: ${thinking_illustration_background_color}
  - id: replying_color
    hex: ${replying_illustration_background_color}
  - id: loading_color
    hex: ${loading_illustration_background_color}
  - id: error_color
    hex: ${error_illustration_background_color}

display:
  - platform: m5cores3_display
    model: ILI9342
    dc_pin: 35
    update_interval: never
    id: m5cores3_lcd
    pages:
      - id: idle_page
        lambda: |-
          it.fill(id(idle_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_idle), ImageAlign::CENTER);
      - id: listening_page
        lambda: |-
          it.fill(id(listening_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_listening), ImageAlign::CENTER);
      - id: thinking_page
        lambda: |-
          it.fill(id(thinking_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_thinking), ImageAlign::CENTER);
      - id: replying_page
        lambda: |-
          it.fill(id(replying_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_replying), ImageAlign::CENTER);
      - id: error_page
        lambda: |-
          it.fill(id(error_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_error), ImageAlign::CENTER);
      - id: no_ha_page
        lambda: |-
          it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_ha), ImageAlign::CENTER);
      - id: no_wifi_page
        lambda: |-
          it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_wifi), ImageAlign::CENTER);
      - id: initializing_page
        lambda: |-
          it.fill(id(loading_color));
          it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_initializing), ImageAlign::CENTER);
      - id: muted_page
        lambda: |-
          it.fill(Color::BLACK);


# 
# Audio
# 
#esp_adf:
board_m5cores3:
m5cores3_audio:
  id: m5cores3_audio_1

microphone:
  - platform: m5cores3_audio
    m5cores3_audio_id: m5cores3_audio_1
    id: m5cores3_mic
    adc_type: external
    i2s_din_pin: 14
    pdm: false

speaker:
  - platform: m5cores3_audio
    m5cores3_audio_id: m5cores3_audio_1
    id: m5cores3_spk
    dac_type: external
    i2s_dout_pin: 13
    mode: mono

# 
# VA
# 
micro_wake_word:
  models: ${micro_wake_word_model}
  on_wake_word_detected:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;
        
voice_assistant:
  id: va
  microphone: m5cores3_mic
  speaker: m5cores3_spk
  use_wake_word: true
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  #vad_threshold: 3
  on_listening:
    - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
    - script.execute: draw_display
  on_stt_vad_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
    - script.execute: draw_display
  on_tts_stream_start:
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - script.execute: draw_display
  on_tts_stream_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
    - script.execute: draw_display
  on_error:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:
          - lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};  
          - script.execute: draw_display
          - delay: 1s
          - if:
              condition:
                switch.is_off: mute
              then:
                - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
              else:
                - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
          - script.execute: draw_display
  on_client_connected: 
    - if:
        condition:
          switch.is_off: mute
        then:
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false; 
    - script.execute: draw_display
  on_client_disconnected:
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};  
    - script.execute: draw_display

select:
  - platform: template
    entity_category: config
    name: Wake word engine location
    id: wake_word_engine_location
    optimistic: true
    restore_value: true
    options:
      - In Home Assistant
      - On device
    initial_option: On device
    on_value:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - wait_until:
                lambda: return id(voice_assistant_phase) == ${voice_assist_muted_phase_id} || id(voice_assistant_phase) == ${voice_assist_idle_phase_id};
            - if:
                condition:
                  lambda: return x == "In Home Assistant";
                then:
                  - micro_wake_word.stop
                  - delay: 30ms
                  - if:
                      condition:
                        switch.is_off: mute
                      then:
                        - lambda: id(va).set_use_wake_word(true);
                        - voice_assistant.start_continuous:
            - if:
                condition:
                  lambda: return x == "On device";
                then:
                  - lambda: id(va).set_use_wake_word(false);
                  - voice_assistant.stop
                  - delay: 30ms
                  - if:
                      condition:
                        switch.is_off: mute
                      then:
                        - micro_wake_word.start      
      


switch:
- platform: template
  name: "LCD Backlight"
  id: switch_lcd_backlight
  restore_mode: "ALWAYS_ON"
  turn_on_action:
    - lambda: |-
        int brightness = id(display_brightness).state; 
        M5.Display.setBrightness(brightness);          
        id(switch_lcd_backlight).publish_state(true);
  turn_off_action:
    - lambda: |-
        M5.Display.setBrightness(0);                  
        id(switch_lcd_backlight).publish_state(false);
- platform: template
  name: Mute
  id: mute
  optimistic: true
  restore_mode: RESTORE_DEFAULT_OFF
  entity_category: config
  on_turn_off:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:      
          - lambda: id(va).set_use_wake_word(true);
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
          - if:
              condition:
                not:
                  - voice_assistant.is_running
              then:
                - voice_assistant.start_continuous
          - script.execute: draw_display
  on_turn_on:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:      
          - voice_assistant.stop
          - lambda: id(va).set_use_wake_word(false);
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
          - script.execute: draw_display