CrowPanel 7 Advance Voice Assistant - mic issues

I’m trying to set this device up as a Voice Assistant with HA. I can’t seem to get mic input working properly.

I stripped down my config to a simple “push button to talk” setup and the Assist pipeline kicks in and listens, but the STT always comes back with the same exact text, no matter what I say to the device. It’s always: " You". A space, and then the word “You”.

Seems like it must be a mic configuration problem. Has anyone else managed to get this device working? I’ve been trying every combination of I2S settings I can find. The only change is that sometimes HA doesn’t detect speech at all, and other times it’s " You". Voice Assist works fine from my phone, so I don’t think it’s a problem on the HA side.

Here’s my currnent yaml:

esphome:
  name: crowpanel
  friendly_name: crowPanel
  platformio_options: 
    build_flags: "-DBOARD_HAS_PSRAM"
    board_build.esp-idf.memory_type: qio_opi
    board_build.flash_mode: dio
  on_boot:
    priority: 600.0
    then:
      - lambda: |-
          uint8_t cmd[1] = { 0x10 };
          id(bus_a).write(0x30, cmd, 1); 
          uint8_t cmd2[1] = { 0xf8 }; // turn on the speaker
          id(bus_a).write(0x30, cmd2, 1); 

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf
    sdkconfig_options: 
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32S3_DATA_CACHE_64KB: y
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y

psram:
  mode: octal
  speed: 80MHz

logger:

api:
  encryption:
    key: ....

ota:
  - platform: esphome
    password: ...

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Aipanel Fallback Hotspot"
    password: ...

captive_portal:

i2c:
  sda: 15
  scl: 16
  scan: true
  id: bus_a

display:
  - platform: rpi_dpi_rgb
    id: main_display
    color_order: RGB
    invert_colors: true
    update_interval: never
    auto_clear_enabled: false
    dimensions:
      width: 800
      height: 480
    de_pin: 42
    hsync_pin: 40
    vsync_pin: 41
    pclk_pin: 39
    pclk_frequency: 20MHz
    hsync_pulse_width: 4
    hsync_front_porch: 8
    hsync_back_porch: 8
    vsync_pulse_width: 4
    vsync_front_porch: 8
    vsync_back_porch: 8
    data_pins:
      red:
        - 7
        - 17
        - 18
        - 3
        - 46
      green:
        - 9
        - 10
        - 11
        - 12
        - 13
        - 14
      blue:
        - 21
        - 47
        - 48
        - 45
        - 38

touchscreen:
  platform: gt911
  id: my_touchscreen
  display: main_display
  update_interval: 50ms
  address: 0x5D
  on_touch:
    - lambda: |-
          ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
              touch.x,
              touch.y,
              touch.x_raw,
              touch.y_raw
              );

i2s_audio:
- id: "i2s_out"
  i2s_lrclk_pin: GPIO6
  i2s_bclk_pin: GPIO5
- id: "i2s_in"
  i2s_lrclk_pin: GPIO2
  i2s_bclk_pin: GPIO19

microphone:
- platform: i2s_audio
  adc_type: external
  #pdm: true
  id: mic_i2s
  channel: right
  i2s_audio_id: i2s_in
  bits_per_sample: 16bit
  i2s_din_pin: GPIO20

speaker:
  - platform: i2s_audio
    id: speaker_i2s
    dac_type: external
    i2s_dout_pin: GPIO4
    i2s_audio_id: i2s_out

media_player:
  - platform: speaker
    name: None
    id: cp_media_player
    announcement_pipeline:
      speaker: speaker_i2s
      format: WAV
    codec_support_enabled: false
    buffer_size: 6000
    volume_min: 0.4
    files:
      - id: timer_finished_wave_file
        file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav

lvgl:
  log_level: INFO
  color_depth: 16
  bg_color: 0xFFFFFF
  text_font: montserrat_26
  widgets:
  - label:
      id: state_label
      x: 10
      y: 10
      text: !lambda return id(va_state).state;
  - button:
      id: start_va
      x: 10
      y: 50
      width: 200
      height: 70
      widgets:
      - label:
          align: CENTER
          text: "Start Voice"
      on_click:
        then:
          - voice_assistant.start:

  - button:
      id: test_speaker
      x: 10
      y: 150
      width: 200
      height: 70
      widgets:
      - label:
          align: CENTER
          text: "Test Speaker"
      on_click:
        then:
          - media_player.speaker.play_on_device_media_file:
              media_file: timer_finished_wave_file
              announcement: true

voice_assistant:
  id: va
  microphone:
    microphone: mic_i2s
    bits_per_sample: 16
    gain_factor: 50
  media_player: cp_media_player
  noise_suppression_level: 2
  auto_gain: 31dBFS
  on_listening:
    - logger.log: "Listening"
  on_stt_vad_end:
    - logger.log: "STT VAD end"
  on_tts_start:
    - logger.log: "TTS Start"
  on_end:
    - logger.log: "VA end"
  on_error:
    - logger.log: "VA error"
  on_client_connected:
    - logger.log: "VA client connect"

text_sensor:
  - platform: homeassistant
    id: va_state
    entity_id:  media_player.crowpanel
    on_value: 
      then:
        - lvgl.widget.refresh: state_label

Hi,
i found this
https://github.com/Incipiens/Adam-Home-Assistant-Snippets/tree/main/ESPHome/Elecrow%20CrowPanel%20Advance%207.0-HMI%20ESP32%20AI%20Display
It works for me.

Thanks for the reply… I don’t think I’d seen that code before!

Unfortunately it didn’t work for me… I tried to get through the VA setup but the wake word was never detected. I also tried just applying his I2S config to my code, and my didn’t work with that configuration. I am wondering if maybe this sample is for a different revision of the hardware… or maybe there’s a hardware issue with my mic.

I wound up ordering a HA voice preview edition… we’ll see how that goes. I’ll probably just use the CrowPanel as a display/touchscreen.

I now remember that the local wake word detection wasn’t working properly. I think it was due to RAM or CPU. I then removed the media player and switched to wake word detection in Home Assistant at the same time. It’s been working ever since. Here’s my code, in case it helps. It also shows what has been understood and what is being responded to on the display (as I don’t have any speakers connected atm).

substitutions:
  loading_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/loading_320_240.png
  idle_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/idle_320_240.png
  listening_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/listening_320_240.png
  thinking_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/thinking_320_240.png
  replying_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/replying_320_240.png
  error_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/error_320_240.png
  timer_finished_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/timer_finished_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"
  voice_assist_timer_finished_phase_id: "20"

  # Add support for non-unicode characters by using better glyphset
  font_glyphsets: "GF_Latin_Core"
  # for Greek use "Noto Sans" for other languages use a compatible font family
  font_family: Figtree

esphome:
  name: esphome-web-11d630
  friendly_name: TabletKüche
  platformio_options:
    build_flags: "-DBOARD_HAS_PSRAM"
    board_build.esp-idf.memory_type: qio_opi
    board_build.flash_mode: dio
  on_boot:
    priority: 600
    then:
      - script.execute: draw_display
      - lambda: |-
          uint8_t cmd[1] = { 0x10 };
          id(bus_a).write(0x30, cmd, 1);  
      - delay: 30s
      - if:
          condition:
            lambda: return id(init_in_progress);
          then:
            - lambda: id(init_in_progress) = false;
            - script.execute: draw_display

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 16MB
  cpu_frequency: 240MHz
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32S3_DATA_CACHE_64KB: y
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y
  variant: esp32s3


ota:
  - platform: esphome
    password: "x"

psram:
  mode: octal
  speed: 80MHz

api:
  on_client_connected:
    - script.execute: draw_display
  on_client_disconnected:
    - script.execute: draw_display

logger:
  level: DEBUG

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:

  on_connect:
    - script.execute: draw_display
  on_disconnect:
    - script.execute: draw_display

captive_portal:

button:
  - platform: factory_reset
    id: factory_reset_btn
    internal: true

touchscreen:
  platform: gt911
  id: touch
  address: 0x5D
  i2c_id: bus_a

i2c:
  sda: 15
  scl: 16
  scan: true
  id: bus_a

i2c_device:
  id: pca9557
  address: 0x30
  i2c_id: bus_a

i2s_audio:
  - id: i2s_audio_bus
    i2s_lrclk_pin: GPIO2
    i2s_bclk_pin: GPIO19

microphone:
  - platform: i2s_audio
    id: box_mic
    sample_rate: 16000
    i2s_audio_id: i2s_audio_bus
    i2s_din_pin: GPIO20
    adc_type: external
    bits_per_sample: 32bit
    channel: stereo
    
#speaker:
#  - platform: i2s_audio
#    id: box_speaker
#    i2s_dout_pin: GPIO5
#    dac_type: external
#    sample_rate: 16000
#    bits_per_sample: 16bit
#    channel: left
#    buffer_duration: 100ms

micro_wake_word:
  id: mww
  models:
    - hey_jarvis
  on_wake_word_detected:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;

voice_assistant:
  id: va
  microphone: box_mic
  micro_wake_word: mww
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  on_listening:
    - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
    - text_sensor.template.publish:
        id: text_request
        state: "..."
    - text_sensor.template.publish:
        id: text_response
        state: "..."
    - lvgl.label.update:
        id: req_label
        text: "..."
    - lvgl.label.update:
        id: reply_label
        text: "..."
    - script.execute: draw_display
  on_stt_vad_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
    - script.execute: draw_display
  on_stt_end:
    - text_sensor.template.publish:
        id: text_request
        state: !lambda return x;
    - lvgl.label.update:
        id: req_label
        text: !lambda 'return x;'
    - script.execute: draw_display
    - script.execute:
        id: send_stt_text_event
        stt_text: !lambda 'return x;'
  on_tts_start:
    - text_sensor.template.publish:
        id: text_response
        state: !lambda return x;
    - lvgl.label.update:
        id: reply_label
        text: !lambda 'return x;'
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - script.execute: draw_display
  on_end:
    # Restart only mWW if enabled; streaming wake words automatically restart
    - if:
        condition:
          - lambda: return id(wake_word_engine_location).state == "On device";
        then:
          - lambda: id(va).set_use_wake_word(false);
          - micro_wake_word.start:
    - script.execute: set_idle_or_mute_phase
    - script.execute: draw_display
    # Clear text sensors
    # - text_sensor.template.publish:
    #     id: text_request
    #     state: ""
    # - text_sensor.template.publish:
    #     id: text_response
    #     state: ""
  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:
    - lambda: id(init_in_progress) = false;
    - script.execute: start_wake_word
    - script.execute: set_idle_or_mute_phase
    - script.execute: draw_display
  on_client_disconnected:
    - script.execute: stop_wake_word
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
    - script.execute: draw_display
  on_timer_started:
    - script.execute: draw_display
  on_timer_cancelled:
    - script.execute: draw_display
  on_timer_updated:
    - script.execute: draw_display
  on_timer_tick:
    - script.execute: draw_display
  on_timer_finished:
    - switch.turn_on: timer_ringing
    - script.execute: draw_display

  on_tts_end:
    - script.execute:
        id: send_tts_uri_event
        tts_uri: !lambda 'return x;'

# --- Define Sensors to fetch data from Home Assistant ---
sensor:
  - platform: homeassistant
    id: ha_temperature_sensor # Give it a local ID
    entity_id: sensor.kueche_temperature
    internal: true # Only used within ESPHome (for display)
    unit_of_measurement: "°C" # Optional: For display consistency
    on_value:
      - lvgl.indicator.update:
          id: val_needle
          value: !lambda return x;     
      - lvgl.label.update:
          id: label_temp
          text:
            format: "%.1f°C"
            args: [ 'x' ]
  - platform: homeassistant
    id: ha_puf_top_sensor # Give it a local ID
    entity_id: sensor.puffer_oben
    internal: true # Only used within ESPHome (for display)
    unit_of_measurement: "°C" # Optional: For display consistency
    on_value:
      - lvgl.bar.update:
          id: puf_top_bar_id
          value: !lambda return x;
          indicator:
            bg_color: !lambda |-
              if (x >= 65) {
                return lv_color_hex(0xFF0000);
              } else if (x > 50) {
                return lv_color_hex(0xebc334);
              } else {
                return lv_color_hex(0x202eab);
              }
      - lvgl.label.update:
          id: puf_top_value_label
          text:
            format: "%.0f°C"
            args: [ 'x' ]
  - platform: homeassistant
    id: ha_puf_mid_sensor # Give it a local ID
    entity_id: sensor.puffer_mitte
    internal: true # Only used within ESPHome (for display)
    unit_of_measurement: "°C" # Optional: For display consistency
    on_value:
      - lvgl.bar.update:
          id: puf_mid_bar_id
          value: !lambda return x;
          indicator:
            bg_color: !lambda |-
              if (x >= 65) {
                return lv_color_hex(0xFF0000);
              } else if (x > 50) {
                return lv_color_hex(0xebc334);
              } else {
                return lv_color_hex(0x202eab);
              }
      - lvgl.label.update:
          id: puf_mid_value_label
          text:
            format: "%.0f°C"
            args: [ 'x' ]
  - platform: homeassistant
    id: ha_puf_bot_sensor # Give it a local ID
    entity_id: sensor.puffer_unten
    internal: true # Only used within ESPHome (for display)
    unit_of_measurement: "°C" # Optional: For display consistency
    on_value:
      - lvgl.bar.update:
          id: puf_bot_bar_id
          value: !lambda return x;
          indicator:
            bg_color: !lambda |-
              if (x >= 65) {
                return lv_color_hex(0xFF0000);
              } else if (x > 50) {
                return lv_color_hex(0xebc334);
              } else {
                return lv_color_hex(0x202eab);
              }
      - lvgl.label.update:
          id: puf_bot_value_label
          text:
            format: "%.0f°C"
            args: [ 'x' ]

binary_sensor:
  - platform: homeassistant
    id: umlaufpunpe
    entity_id: switch.steckdose_heizraum_switch
    publish_initial_state: true
    on_state:
      then:
        lvgl.widget.update:
          id: umlauf_btn
          state:
            checked: !lambda return x;

time:
  - platform: homeassistant
    id: time_comp
    on_time_sync:
      - script.execute: time_update
    on_time:
      - minutes: '*'
        seconds: 0
        then:
          - script.execute: time_update

script:
  - id: time_update
    then:
      - lvgl.label.update:
          id: time_value_label
          text: !lambda |-
            return id(time_comp).now().strftime("%H:%M");                  
      - lvgl.label.update:
          id: date_value_label
          text: !lambda |-
            static const char * const mon_names[] = {"JAN", "FEB", "MAR", "APR", "MAI", "JUN",
                                                     "JUL", "AUG", "SEP", "OKT", "NOV", "DEZ"};
            static char date_buf[8];
            auto now = id(time_comp).now();
            snprintf(date_buf, sizeof(date_buf), "%s %2d", mon_names[now.month-1], now.day_of_month);
            return date_buf;
  - id: send_tts_uri_event
    parameters:
      tts_uri: string
    then:
      - homeassistant.event:
          event: esphome.tts_uri
          data:
            uri: !lambda return tts_uri;
  - id: send_stt_text_event
    parameters:
      stt_text: string
    then:
      - homeassistant.event:
          event: esphome.stt_text
          data:
            text: !lambda return stt_text;
  - id: draw_display
    then:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - if:
                condition:
                  wifi.connected:
                then:
                  - if:
                      condition:
                        api.connected:
                      then:
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_listening_phase_id};'  }, then: { lvgl.page.show: listening_page } }
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_thinking_phase_id};'   }, then: { lvgl.page.show: thinking_page  } }
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_replying_phase_id};'   }, then: { lvgl.page.show: replying_page  } }
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_error_phase_id};'      }, then: { lvgl.page.show: error_page     } }
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_muted_phase_id};'      }, then: { lvgl.page.show: muted_page     } }
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_not_ready_phase_id};'  }, then: { lvgl.page.show: no_ha_page     } }
                      - if: { condition: { lambda: 'return id(voice_assistant_phase) == ${voice_assist_timer_finished_phase_id};' }, then: { lvgl.page.show: timer_finished_page } }
                      # default
                      - if:
                          condition:
                            lambda: >
                              return id(voice_assistant_phase) != ${voice_assist_listening_phase_id} &&
                                    id(voice_assistant_phase) != ${voice_assist_thinking_phase_id} &&
                                    id(voice_assistant_phase) != ${voice_assist_replying_phase_id} &&
                                    id(voice_assistant_phase) != ${voice_assist_error_phase_id} &&
                                    id(voice_assistant_phase) != ${voice_assist_muted_phase_id} &&
                                    id(voice_assistant_phase) != ${voice_assist_not_ready_phase_id} &&
                                    id(voice_assistant_phase) != ${voice_assist_timer_finished_phase_id};
                          then:
                            - lvgl.page.show: idle_page
                      else:
                        - lvgl.page.show: no_ha_page 
                else:
                  - lvgl.page.show: no_wifi_page
          else:
            - lvgl.page.show: initializing_page

  - id: fetch_first_active_timer
    then:
      - lambda: |
          const auto timers = id(va).get_timers();
          auto output_timer = timers.begin()->second;
          for (auto &iterable_timer : timers) {
            if (iterable_timer.second.is_active && iterable_timer.second.seconds_left <= output_timer.seconds_left) {
              output_timer = iterable_timer.second;
            }
          }
          id(global_first_active_timer) = output_timer;
  - id: check_if_timers_active
    then:
      - lambda: |
          const auto timers = id(va).get_timers();
          bool output = false;
          if (timers.size() > 0) {
            for (auto &iterable_timer : timers) {
              if(iterable_timer.second.is_active) {
                output = true;
              }
            }
          }
          id(global_is_timer_active) = output;
  - id: fetch_first_timer
    then:
      - lambda: |
          const auto timers = id(va).get_timers();
          auto output_timer = timers.begin()->second;
          for (auto &iterable_timer : timers) {
            if (iterable_timer.second.seconds_left <= output_timer.seconds_left) {
              output_timer = iterable_timer.second;
            }
          }
          id(global_first_timer) = output_timer;
  - id: check_if_timers
    then:
      - lambda: |
          const auto timers = id(va).get_timers();
          bool output = false;
          if (timers.size() > 0) {
            output = true;
          }
          id(global_is_timer) = output;

  - id: draw_timer_timeline
    then:
      - lambda: |
          id(check_if_timers_active).execute();
          id(check_if_timers).execute();
          if (id(global_is_timer_active)){
            id(fetch_first_active_timer).execute();
            int active_pixels = round( 320 * id(global_first_active_timer).seconds_left / max(id(global_first_active_timer).total_seconds , static_cast<uint32_t>(1)) );
            if (active_pixels > 0){
              id(main_display).filled_rectangle(0 , 225 , 320 , 15 , Color::WHITE );
              id(main_display).filled_rectangle(0 , 226 , active_pixels , 13 , id(active_timer_color) );
            }
          } else if (id(global_is_timer)){
            id(fetch_first_timer).execute();
            int active_pixels = round( 320 * id(global_first_timer).seconds_left / max(id(global_first_timer).total_seconds , static_cast<uint32_t>(1)));
            if (active_pixels > 0){
              id(main_display).filled_rectangle(0 , 225 , 320 , 15 , Color::WHITE );
              id(main_display).filled_rectangle(0 , 226 , active_pixels , 13 , id(paused_timer_color) );
            }
          }
  - id: draw_active_timer_widget
    then:
      - lambda: |
          id(check_if_timers_active).execute();
          if (id(global_is_timer_active)){
            id(main_display).filled_rectangle(80 , 40 , 160 , 50 , Color::WHITE );
            id(main_display).rectangle(80 , 40 , 160 , 50 , Color::BLACK );

            id(fetch_first_active_timer).execute();
            int hours_left = floor(id(global_first_active_timer).seconds_left / 3600);
            int minutes_left = floor((id(global_first_active_timer).seconds_left - hours_left * 3600) / 60);
            int seconds_left = id(global_first_active_timer).seconds_left - hours_left * 3600 - minutes_left * 60 ;
            auto display_hours = (hours_left < 10 ? "0" : "") + std::to_string(hours_left);
            auto display_minute = (minutes_left < 10 ? "0" : "") + std::to_string(minutes_left);
            auto display_seconds = (seconds_left  < 10 ? "0" : "") + std::to_string(seconds_left) ;

            std::string display_string = "";
            if (hours_left > 0) {
              display_string = display_hours + ":" + display_minute;
            } else {
              display_string = display_minute + ":" + display_seconds;
            }
            id(main_display).printf(120, 47, id(font_timer), Color::BLACK, "%s", display_string.c_str());
          }
  # Starts either mWW or the streaming wake word, depending on the configured location
  - id: start_wake_word
    then:
      - if:
          condition:
            and:
              - not:
                  - voice_assistant.is_running:
              - lambda: return id(wake_word_engine_location).state == "On device";
          then:
            - lambda: id(va).set_use_wake_word(false);
            - micro_wake_word.start:
      - if:
          condition:
            and:
              - not:
                  - voice_assistant.is_running:
              - lambda: return id(wake_word_engine_location).state == "In Home Assistant";
          then:
            - lambda: id(va).set_use_wake_word(true);
            - voice_assistant.start_continuous:
  # Stops either mWW or the streaming wake word, depending on the configured location
  - id: stop_wake_word
    then:
      - if:
          condition:
            lambda: return id(wake_word_engine_location).state == "In Home Assistant";
          then:
            - lambda: id(va).set_use_wake_word(false);
            - voice_assistant.stop:
      - if:
          condition:
            lambda: return id(wake_word_engine_location).state == "On device";
          then:
            - micro_wake_word.stop:
  # Set the voice assistant phase to idle or muted, depending on if the software mute switch is activated
  - id: set_idle_or_mute_phase
    then:
      - 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};

switch:
  - platform: gpio
    name: Speaker Enable
    pin: GPIO4
    restore_mode: RESTORE_DEFAULT_ON
    entity_category: config
    disabled_by_default: true
  - platform: template
    name: Stream
    optimistic: true
    on_turn_on:
      - microphone.capture:
    on_turn_off:
      - microphone.stop_capture:
  - platform: template
    name: Mute
    id: mute
    icon: "mdi:microphone-off"
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    entity_category: config
    on_turn_off:
      - microphone.unmute:
      - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
      - script.execute: draw_display
    on_turn_on:
      - microphone.mute:
      - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
      - script.execute: draw_display
  - platform: template
    id: timer_ringing
    optimistic: true
    internal: true
    restore_mode: ALWAYS_OFF
    on_turn_off:
      # Turn off the repeat mode and disable the pause between playlist items
      - delay: 15min
      - switch.turn_off: timer_ringing

select:
  - platform: template
    entity_category: config
    name: Wake word engine location
    id: wake_word_engine_location
    icon: "mdi:account-voice"
    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: 500ms
                  - 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: 500ms
                  - if:
                      condition:
                        switch.is_off: mute
                      then:
                        - micro_wake_word.start
output:
  - platform: template
    id: backlight_output
    type: binary
    write_action:
      - lambda: |-
          // Write a single byte
          static uint8_t data[] = {0x10};
          static uint8_t data_on[] = {0x06};
          static uint8_t data_off[] = {0x05};
          id(pca9557).write(data, sizeof(data));          
          delay(20);          
          if (state) {
            id(pca9557).write(data_on, sizeof(data_on));
          } else {
            id(pca9557).write(data_off, sizeof(data_off));
          }

light:
  - platform: binary
    name: "Backlight"
    output: backlight_output
    id: backlight
    restore_mode: ALWAYS_ON

globals:
  - id: init_in_progress
    type: bool
    restore_value: false
    initial_value: "true"
  - id: voice_assistant_phase
    type: int
    restore_value: false
    initial_value: ${voice_assist_not_ready_phase_id}
  - id: global_first_active_timer
    type: voice_assistant::Timer
    restore_value: false
  - id: global_is_timer_active
    type: bool
    restore_value: false
  - id: global_first_timer
    type: voice_assistant::Timer
    restore_value: false
  - id: global_is_timer
    type: bool
    restore_value: false

image:
  - file: mdi:sun-wireless-outline
    id: solar_power_icon
    resize: 50x50
    type: grayscale
  - file: ${error_illustration_file}
    id: casita_error
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: ${idle_illustration_file}
    id: casita_idle
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: ${listening_illustration_file}
    id: casita_listening
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: ${thinking_illustration_file}
    id: casita_thinking
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: ${replying_illustration_file}
    id: casita_replying
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: ${timer_finished_illustration_file}
    id: casita_timer_finished
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: ${loading_illustration_file}
    id: casita_initializing
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: https://github.com/esphome/wake-word-voice-assistants/raw/main/error_box_illustrations/error-no-wifi.png
    id: error_no_wifi
    resize: 320x240
    type: RGB565
    transparency: alpha_channel
  - file: https://github.com/esphome/wake-word-voice-assistants/raw/main/error_box_illustrations/error-no-ha.png
    id: error_no_ha
    resize: 320x240
    type: RGB565
    transparency: alpha_channel

font:
  - file: "arialr.ttf"
    id: font_clock
    size: 120
  - file: "arialr.ttf" # Example: Add another size if needed
    id: font_medium
    size: 24
  - file: "arialr.ttf" # Example: Add another size if needed
    id: font_large
    size: 32
  - file: "arialr.ttf" # Example: Add another size if needed
    id: font_small
    size: 1
  - file:
      type: gfonts
      family: ${font_family}
      weight: 300
      italic: true
    id: font_request
    size: 15
    glyphsets:
      - ${font_glyphsets}
  - file:
      type: gfonts
      family: ${font_family}
      weight: 300
    id: font_response
    size: 15
    glyphsets:
      - ${font_glyphsets}
  - file:
      type: gfonts
      family: ${font_family}
      weight: 300
    id: font_timer
    size: 30
    glyphsets:
      - ${font_glyphsets}

text_sensor:
  - id: text_request
    platform: template
    on_value:
      lambda: |-
        if(id(text_request).state.length()>32) {
          std::string name = id(text_request).state.c_str();
          std::string truncated = esphome::str_truncate(name.c_str(),31);
          id(text_request).state = (truncated+"...").c_str();
        }

  - id: text_response
    platform: template
    on_value:
      lambda: |-
        if(id(text_response).state.length()>32) {
          std::string name = id(text_response).state.c_str();
          std::string truncated = esphome::str_truncate(name.c_str(),31);
          id(text_response).state = (truncated+"...").c_str();
        }

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}
  - id: active_timer_color
    hex: "26ed3a"
  - id: paused_timer_color
    hex: "3b89e3"

display:
  - platform: rpi_dpi_rgb
    id: main_display
    color_order: RGB
    invert_colors: true
    update_interval: never
    auto_clear_enabled: false
    dimensions:
      width: 800
      height: 480
    de_pin: 42
    hsync_pin: 40
    vsync_pin: 41
    pclk_pin: 39
    pclk_frequency: 20MHz
    hsync_pulse_width: 4
    hsync_front_porch: 8
    hsync_back_porch: 8
    vsync_pulse_width: 4
    vsync_front_porch: 8
    vsync_back_porch: 8
    data_pins:
      red:
        - 7
        - 17
        - 18
        - 3
        - 46
      green:
        - 9
        - 10
        - 11
        - 12
        - 13
        - 14
      blue:
        - 21
        - 47
        - 48
        - 45
        - 38

i have to split the yaml

lvgl part of yaml

lvgl:
  style_definitions:   
    - id: meter_style
      border_width: 0
      outline_width: 0
      align: center
      bg_color: 0
    - id: title_style
      text_font: font_large
      align: center
      text_color: 0xFFFFFF
      bg_opa: TRANSP
      bg_color: 0
      radius: 4
      pad_all: 2
    - id: detail_style
      text_font: font_medium
      align: center
      text_color: 0xFFFFFF
      bg_opa: TRANSP
      bg_color: 0
      radius: 4
      pad_all: 2

    # --- Base Styles ---
    - id: base_panel_style
      bg_color: 0x000000 # Black background
      border_width: 0
      radius: 0
      pad_all: 0 # No padding for main panels

    # --- Section Styles ---
    - id: section_panel_style
      bg_color: 0x111111 # Dark grey background for sections
      border_width: 1
      border_color: 0x444444
      radius: 10
      pad_all: 10 # Padding inside sections

    # --- Label Styles ---
    - id: title_label_style
      text_font: font_medium # Reference font by id prepended with $
      text_color: 0xCCCCCC # Light grey text
      align: CENTER
      bg_opa: TRANSP

    - id: sensor_value_style
      text_font: font_large # Reference font by id prepended with $
      text_color: 0xFFFFFF # White text
      align: CENTER
      bg_opa: TRANSP

    - id: button_label_style
      text_font: font_medium # Reference font by id prepended with $
      text_color: 0xFFFFFF
      align: CENTER
      bg_opa: TRANSP

    - id: clock_label_style
      text_font: font_clock # Reference font by id prepended with $
      text_color: 0xFFFFFF
      align: CENTER      

    # --- Button Style ---
    - id: button_style
      bg_color: 0x0000FF # Blue background
      radius: 15
      border_width: 1
      border_color: 0xCCCCCC
      pad_all: 10

  top_layer:
    widgets:
      - bar:
          id: timer_timeline
          x: 0
          y: 460
          width: 800
          height: 20
          min_value: 0
          max_value: 100
          value: !lambda |-
            return (int) id(global_first_active_timer).seconds_left;

          bg_color: 0x202020
          pad_all: 0
          radius: 0

          indicator:
            bg_color: 0xFFFFFF
            radius: 0

  pages:

    # IDLE
    #- id: idle_page
    #  bg_color: idle_color
    #  widgets:
    #    - image:
    #        src: casita_idle
    #        align: CENTER

    # LISTENING
    - id: listening_page
      bg_color: listening_color
      widgets:
        - image:
            src: casita_listening
            align: CENTER

    # ERROR
    - id: error_page
      bg_color: listening_color
      widgets:
        - image:
            src: casita_error
            align: CENTER

    # NO HA
    - id: no_ha_page
      bg_color: listening_color
      widgets:
        - image:
            src: error_no_ha
            align: CENTER

    # NO WI-FI
    - id: no_wifi_page
      bg_color: listening_color
      widgets:
        - image:
            src: error_no_wifi
            align: CENTER

    # MUTED
    - id: muted_page
      bg_color: listening_color
      widgets:
        - image:
            src: casita_idle
            align: CENTER
      
    # INIT
    - id: initializing_page
      bg_color: listening_color
      widgets:
        - image:
            src: casita_initializing
            align: CENTER

    # NO WI-FI
    - id: timer_finished_page
      bg_color: listening_color
      widgets:
        - image:
            src: casita_initializing
            align: CENTER

    # THINKING
    - id: thinking_page
      bg_color: 0x2A2A2A
      # widgets:
      #   - image:
      #       src: casita_thinking
      #       align: CENTER

      #   - obj:
      #       id: req_box
      #       x: 20
      #       y: 20
      #       width: 280
      #       height: 30
      #       bg_color: 0xFFFFFF
      #       border_color: 0x000000
      #       border_width: 2
      #       radius: 0
      #       pad_all: 0
      #       widgets:
      #         - label:
      #             id: req_label
      #             x: 10
      #             y: 5
      #             text_font: font_request
      #             width: 260
      #             long_mode: SCROLL_CIRCULAR    # or DOT / CLIP
      #             text: !lambda |-
      #               return id(text_request).state;

    # REPLYING
    - id: replying_page
      bg_color: replying_color
      # widgets:
      #   - image:
      #       src: casita_replying
      #       align: CENTER

      #   - obj:
      #       id: reply_box
      #       x: 20
      #       y: 20
      #       width: 280
      #       height: 30
      #       bg_color: 0xFFFFFF
      #       border_color: 0x000000
      #       border_width: 2
      #       radius: 0
      #       pad_all: 0
      #       widgets:
      #         - label:
      #             id: reply_label
      #             x: 10
      #             y: 5
      #             text_font: font_request
      #             width: 260
      #             long_mode: SCROLL_CIRCULAR
      #             text: !lambda |-
      #               return id(text_request).state;

    - id: idle_page
      bg_color: 0x000000
      widgets:
        - obj: # a properly placed coontainer object for all these controls
            align: CENTER
            width: 800
            height: 480
            pad_all: 6
            bg_opa: TRANSP
            border_opa: TRANSP
            layout: # enable the GRID layout for the children widgets
              type: GRID # split the rows and the columns proportionally
              grid_columns: [FR(50), FR(25), FR(25)] # equal
              grid_rows: [FR(1), FR(1), FR(1)] # like percents
            widgets:
              # --- Time Section ---
              - obj:
                  id: time_section
                  styles: section_panel_style      
                  height: 100%
                  width: 100%       
                  grid_cell_column_pos: 0 # place the widget in
                  grid_cell_row_pos: 0 # the corresponding cell
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH                      
                  widgets:   
                    - label:
                        id: date_value_label
                        styles: title_label_style                        
                        align: TOP_MID                        
                        text: "dd.mm.yyyy"                 
                    - label:
                        id: time_value_label
                        styles: clock_label_style
                        align: CENTER  
                        y: 15                      
                        text: "hh:mm"
              # --- Temperature Section ---
              - obj:
                  id: temp_section
                  styles: section_panel_style       
                  height: 100%
                  width: 100%                                    
                  scrollbar_mode: "OFF"
                  grid_cell_column_pos: 1 # place the widget in
                  grid_cell_row_pos: 0 # the corresponding cell
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH            
                  widgets:
                    - meter: # Gradient color  arc
                        height: 100%
                        width: 100%
                        border_width: 0
                        outline_width: 0
                        align: center
                        bg_color: 0
                        scales:
                          angle_range: 180
                          range_to: 100
                          range_from: 0
                          ticks:
                            count: 0
                          indicators:
                            - line:
                                id: val_needle
                                width: 8
                                color: 0xFFFFFF
                                r_mod: 12
                                value: 50
                            - arc:
                                color: 0xFF3000
                                r_mod: 10
                                width: 20
                                start_value: 0
                                end_value: 40
                            - arc:
                                color: 0x00FF00
                                r_mod: 10
                                width: 20
                                start_value: 40
                                end_value: 100
                    
                    - arc: # black arc to erase middle part of meter indicator line
                        height: 120
                        width: 120
                        y: 18
                        align: center
                        arc_color: 0x000000
                        #radius: 60
                        arc_width: 60
                        start_angle: 180
                        end_angle: 360
                        
                    - label: # gauge lower and higher range indicators
                        styles: detail_style
                        y: 31
                        x: -70
                        text: "0"
                    - label:
                        styles: detail_style
                        y: 31
                        x: 70
                        text: "100"
                    - label:  # value label
                        styles: title_style
                        id: label_temp
                        y: -5
                    - image:
                        src: solar_power_icon
                        id: img_solar_power
                        align: center
                        image_recolor: 0xFFFF00
                        image_recolor_opa: 100%
                        y: 50

              # --- Puffer Section ---
              - obj:
                  id: humidity_section
                  styles: section_panel_style      
                  height: 100%
                  width: 100%       
                  grid_cell_column_pos: 2 # place the widget in
                  grid_cell_row_pos: 0 # the corresponding cell
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH                      
                  widgets:                                            
                    - label:
                        styles: title_label_style
                        y: -5
                        align: TOP_MID
                        text: "Puffer"
                    - bar:                       
                        id: puf_top_bar_id
                        y: 15
                        value: 20
                        width: 100%
                        height: 35
                        min_value: 20
                        max_value: 80
                    - label:
                        id: puf_top_value_label
                        styles: sensor_value_style                        
                        align: TOP_MID
                        y: 18
                        text: !lambda |-
                          if (id(ha_puf_top_sensor).has_state()) {
                            return esphome::to_string(id(ha_puf_top_sensor).state) + id(ha_puf_top_sensor).get_unit_of_measurement();
                          } else {
                            return "--- " + id(ha_puf_top_sensor).get_unit_of_measurement();
                          }
                    - bar:                       
                        id: puf_mid_bar_id
                        y: 50
                        value: 20
                        width: 100%
                        height: 35
                        min_value: 20
                        max_value: 80
                    - label:
                        id: puf_mid_value_label
                        styles: sensor_value_style                        
                        align: TOP_MID
                        y: 53
                        text: !lambda |-
                          if (id(ha_puf_mid_sensor).has_state()) {
                            return esphome::to_string(id(ha_puf_mid_sensor).state) + id(ha_puf_mid_sensor).get_unit_of_measurement();
                          } else {
                            return "--- " + id(ha_puf_mid_sensor).get_unit_of_measurement();
                          }
                    - bar:                       
                        id: puf_bot_bar_id
                        y: 85
                        value: 20
                        width: 100%
                        height: 35
                        min_value: 20
                        max_value: 80
                    - label:
                        id: puf_bot_value_label
                        styles: sensor_value_style                        
                        align: TOP_MID
                        y: 88
                        text: !lambda |-
                          if (id(ha_puf_bot_sensor).has_state()) {
                            return esphome::to_string(id(ha_puf_bot_sensor).state) + id(ha_puf_bot_sensor).get_unit_of_measurement();
                          } else {
                            return "--- " + id(ha_puf_bot_sensor).get_unit_of_measurement();
                          }
              # --- Button Section ---
              - obj:
                  id: button_section
                  styles: section_panel_style      
                  height: 100%
                  width: 100%       
                  grid_cell_column_pos: 0 # place the widget in
                  grid_cell_row_pos: 1 # the corresponding cell
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH                                        
                  widgets:
                    - button:
                        id: umlauf_btn
                        align: CENTER
                        width: 100
                        height: 50
                        checkable: true
                        widgets:
                          - label:
                              align: CENTER
                              text: 'Umlaufpumpe'
                        on_click:
                          - homeassistant.action:
                              action: switch.toggle
                              data:
                                entity_id: switch.steckdose_heizraum_switch
              # --- Voice Assistant ---          
              - obj:
                  id: voice_box
                  styles: section_panel_style      
                  height: 100%
                  width: 100%       
                  grid_cell_column_pos: 0 # place the widget in
                  grid_cell_row_pos: 2 # the corresponding cell
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  bg_color: 0xCCCCCC                  
                  widgets:
                    - label:
                        id: req_label
                        x: 5
                        y: 5
                        text_font: font_request
                        width: 260
                        long_mode: SCROLL_CIRCULAR    # or DOT / CLIP
                        text: !lambda |-
                          return id(text_request).state;
                    - label:
                        id: reply_label
                        x: 5
                        y: 30
                        text_font: font_request
                        width: 260
                        long_mode: SCROLL_CIRCULAR    # or DOT / CLIP
                        text: !lambda |-
                          return id(text_response).state;