Waveshare 3.49 and mipi_spi

You’ll need to compile with the dev version, there are other changes merged.

I updated the PR so should fix that.

external_components:
  - source: github://pr#11680
    components: [lvgl, const]
    refresh: 1h

sorry was at work, but tested now and seem you fixed the problem :slight_smile:
compiled with normal ESPHome 2025.10.3.
you better be the one to close as solution, last time i did was too early :wink:

thanks for sticking with this issue :+1:

sidenote, i used the same display model as before, not the one in your example:

spi:
  id: display_qspi
  type: quad
  clk_pin: 10
  data_pins: [11, 12, 13, 14]

display:
  - platform: mipi_spi
    id: main_display
    model: WAVESHARE-ESP32-S3-TOUCH-LCD-3.49
    rotation: 90
lvgl:

I had, or rather still have, this green stripe on my motherboard. I managed to get rid of it using AI testing by shifting the image by a few pixels. Thomas, maybe that will help a little? However, other things didn’t work after that.
waveshare 1.75 Amoled Screen 466x466 resolution

substitutions:
  name: waveshare_amoled_175
  friendly_name: "Xiaozhi 1.75 AMOLED"
  
  # Xiaozhi Character Images
  imagemodel: "Eyes"  # Options: Gwen, Casita, Eyes, Robot, etc.
  imagewidth: "360"
  imageheight: "360"
  rotate_display: "0"

  startup_sound: "Home_Connected"

  # Xiaozhi Image URLs - Using 360x360 (best balance of quality and RAM usage)
  loading_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/loading.png
  idle_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/idle.png
  listening_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/listening.png
  thinking_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/thinking.png
  replying_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/replying.png
  error_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/error.png
  timer_finished_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/timer_finished.png
  mute_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/mute.png
  # Extra image for blinking animation
  blink_illustration_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/images/Eyes/360x360/mute.png
  
  startup_sound_file: https://github.com/RealDeco/xiaozhi-esphome/raw/main/sounds/${startup_sound}.flac

  # Colors
  loading_illustration_background_color: "000000"
  idle_illustration_background_color: "000000"
  listening_illustration_background_color: "000000"
  thinking_illustration_background_color: "000000"
  replying_illustration_background_color: "000000"
  error_illustration_background_color: "000000"

  # Phase IDs
  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"

  font_glyphsets: "GF_Latin_Core"
  font_family: Figtree
  light5_entity: "light.none"
  
  # External Media Player (e.g., your Sonos)
  external_media_player_entity: "media_player.sonos_wohnzimmer"  # Change to your Sonos entity ID

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  platformio_options:
    board_build.flash_mode: dio
    board_build.f_flash: 80000000L
    board_build.f_cpu: 240000000L
  on_boot:
    priority: 600
    then:
      - switch.turn_on: speaker_enable
      - delay: 500ms
      - script.execute: draw_display
      - 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
  framework:
    type: esp-idf

psram:
  mode: octal
  speed: 80MHz

# Required for CST9217 touchscreen driver
external_components:
  - source:
      type: git
      url: https://github.com/shelson/esphome-cst9217

api:
  on_client_connected:
    - script.execute: draw_display
    # Startup sound disabled due to download issues
    # - if:
    #     condition:
    #       switch.is_on: startup_sound_switch
    #     then:
    #       - delay: 300ms
    #       - script.execute:
    #           id: play_sound
    #           priority: true
    #           sound_file: !lambda return id(ready_sound);
    #       - delay: 300ms
  on_client_disconnected:
    - script.execute: draw_display

ota:
  - platform: esphome

logger:
  level: INFO

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "${name} Fallback"
    password: "fallbackpass"
  on_connect:
    - script.execute: draw_display
  on_disconnect:
    - script.execute: draw_display

captive_portal:

# --- HARDWARE ---

i2c:
  - id: bus_a
    sda: GPIO15
    scl: GPIO14
    scan: true

i2s_audio:
  - id: i2s_audio_bus
    i2s_lrclk_pin:
      number: GPIO45
      ignore_strapping_warning: true
    i2s_bclk_pin: GPIO9
    i2s_mclk_pin: GPIO42

audio_adc:
  - platform: es7210
    i2c_id: bus_a
    id: es7210_adc
    address: 0x40
    mic_gain: 30db
    bits_per_sample: 16bit
    sample_rate: 16000

audio_dac:
  - platform: es8311
    i2c_id: bus_a
    id: es8311_dac
    address: 0x18
    bits_per_sample: 16bit
    sample_rate: 16000

microphone:
  - platform: i2s_audio
    id: box_mic
    i2s_audio_id: i2s_audio_bus
    sample_rate: 16000
    i2s_din_pin: GPIO10
    bits_per_sample: 16bit
    adc_type: external
    pdm: false

speaker:
  - platform: i2s_audio
    id: box_speaker
    i2s_audio_id: i2s_audio_bus
    dac_type: external
    i2s_dout_pin: GPIO8
    sample_rate: 16000
    bits_per_sample: 16bit
    audio_dac: es8311_dac
    buffer_duration: 64ms

media_player:
  - platform: speaker
    name: None
    id: external_media_player
    announcement_pipeline:
      speaker: box_speaker
      format: FLAC
      sample_rate: 48000
      num_channels: 1
    # Sound files temporarily disabled due to download issues
    # files:
    #   - id: timer_finished_sound
    #     file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/timer_finished.flac
    #   - id: wake_word_triggered_sound
    #     file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/wake_word_triggered.flac
    #   - id: ready_sound
    #     file: ${startup_sound_file}
    on_announcement:
      - if:
          condition:
            - microphone.is_capturing:
          then:
            - script.execute: stop_wake_word
            - if:
                condition:
                  - lambda: return id(wake_word_engine_location).state == "In Home Assistant";
                then:
                  - wait_until:
                      - not:
                          voice_assistant.is_running:
      - if:
          condition:
            not:
              voice_assistant.is_running:
          then:
            - if:
                condition:
                  lambda: 'return id(playing_internal_sound);'
                then:
                  - lambda: 'id(playing_internal_sound) = false;'
                else:
                  - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
                  - script.execute: draw_display
    on_idle:
      - if:
          condition:
            not:
              voice_assistant.is_running:
          then:
            - script.execute: start_wake_word
            - script.execute: set_idle_or_mute_phase
            - script.execute: draw_display

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

binary_sensor:
  - platform: template
    name: "Touch Button"
    id: touch_input
    on_multi_click:
      - timing:
          - ON for 40ms to 400ms
          - OFF for at least 330ms
        then:
          - if:
              condition:
                switch.is_on: timer_ringing
              then:
                - switch.turn_off: timer_ringing
              else:
                - if:
                    condition:
                      lambda: return id(wake_word_engine_location).state == "On device";
                    then:
                      - if:
                          condition:
                            not:
                              voice_assistant.is_running:
                          then:
                            - voice_assistant.start:
                          else:
                            - voice_assistant.stop:

  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      ignore_strapping_warning: true
      inverted: true
    id: boot_button
    internal: true
    on_multi_click:
      - timing:
          - ON for at least 50ms
          - OFF for at least 50ms
        then:
          - if:
              condition:
                switch.is_on: timer_ringing
              then:
                - switch.turn_off: timer_ringing
              else:
                - if:
                    condition:
                      not:
                        voice_assistant.is_running:
                    then:
                      - voice_assistant.start:
                    else:
                      - voice_assistant.stop:
      - timing:
          - ON for at least 10s
        then:
          - button.press: factory_reset_btn

micro_wake_word:
  id: mww
  models:
    - okay_nabu
    - hey_jarvis
    - alexa
  on_wake_word_detected:
    # Sound disabled due to download issues
    # - if:
    #     condition:
    #       switch.is_on: wake_sound
    #     then:
    #       - script.execute:
    #           id: play_sound
    #           priority: true
    #           sound_file: !lambda return id(wake_word_triggered_sound);
    #       - delay: 300ms
    - voice_assistant.start:
        wake_word: !lambda return wake_word;

voice_assistant:
  id: va
  microphone: box_mic
  media_player: external_media_player
  micro_wake_word: mww
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 1.5
  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: "..."
    - 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;
    - script.execute: draw_display
  on_tts_start:
    - text_sensor.template.publish:
        id: text_response
        state: !lambda return x;
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - script.execute: draw_display
  on_end:
    - wait_until:
        condition:
          - media_player.is_announcing:
        timeout: 0.5s
    - wait_until:
        - and:
            - not:
                media_player.is_announcing:
            - not:
                speaker.is_playing:
    - 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
    - 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:
  - 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(main_display).show_page(listening_page);
                                id(main_display).update();
                                break;
                              case ${voice_assist_thinking_phase_id}:
                                id(main_display).show_page(thinking_page);
                                id(main_display).update();
                                break;
                              case ${voice_assist_replying_phase_id}:
                                id(main_display).show_page(replying_page);
                                id(main_display).update();
                                break;
                              case ${voice_assist_error_phase_id}:
                                id(main_display).show_page(error_page);
                                id(main_display).update();
                                break;
                              case ${voice_assist_muted_phase_id}:
                                id(main_display).show_page(muted_page);
                                id(main_display).update();
                                break;
                              case ${voice_assist_not_ready_phase_id}:
                                id(main_display).show_page(no_ha_page);
                                id(main_display).update();
                                break;
                              case ${voice_assist_timer_finished_phase_id}:
                                id(main_display).show_page(timer_finished_page);
                                id(main_display).update();
                                break;
                              default:
                                id(main_display).show_page(idle_page);
                                id(main_display).update();
                            }
                      else:
                        - display.page.show: no_ha_page
                        - component.update: main_display
                else:
                  - display.page.show: no_wifi_page
                  - component.update: main_display
          else:
            - display.page.show: initializing_page
            - component.update: main_display

  - 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:

  - 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:

  - 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};

  - id: play_sound
    parameters:
      priority: bool
      sound_file: "audio::AudioFile*"
    then:
      - lambda: |-
          id(playing_internal_sound) = true;
          if (priority) {
            id(external_media_player)
              ->make_call()
              .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP)
              .set_announcement(true)
              .perform();
          }
          if ( (id(external_media_player).state != media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING ) || priority) {
            id(external_media_player)
              ->play_file(sound_file, true, false);
          }

switch:
  - 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:
      - lambda: |-
              id(external_media_player)
                ->make_call()
                .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF)
                .set_announcement(true)
                .perform();
              id(external_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0);
      - media_player.stop:
          announcement: true
    on_turn_on:
      - lambda: |-
            id(external_media_player)
              ->make_call()
              .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE)
              .set_announcement(true)
              .perform();
            id(external_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000);
      # Timer sound disabled due to download issues
      # - media_player.speaker.play_on_device_media_file:
      #     media_file: timer_finished_sound
      #     announcement: true
      - delay: 15min
      - switch.turn_off: timer_ringing

  - platform: gpio
    name: Speaker Enable
    id: speaker_enable
    icon: "mdi:speaker"
    entity_category: config
    pin:
      number: GPIO46
      ignore_strapping_warning: true
    restore_mode: RESTORE_DEFAULT_ON

  - platform: template
    id: wake_sound
    name: Wake sound
    icon: "mdi:bullhorn"
    entity_category: config
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON

  - platform: template
    id: startup_sound_switch
    name: Startup sound
    icon: "mdi:card-text-outline"
    entity_category: config
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON

  - platform: template
    id: show_text
    name: Show Text
    icon: "mdi:card-text-outline"
    entity_category: config
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON

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

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
  - id: playing_internal_sound
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: is_blinking
    type: bool
    restore_value: no
    initial_value: 'false'

# Blinking animation interval
interval:
  - interval: 4s
    then:
      - lambda: |-
          // Only blink during idle phase
          if (id(voice_assistant_phase) == ${voice_assist_idle_phase_id} && !id(is_blinking)) {
            id(is_blinking) = true;
            // Show blink
            id(main_display).show_page(blink_page);
            id(main_display).update();
          }
      - delay: 150ms
      - lambda: |-
          if (id(is_blinking)) {
            id(is_blinking) = false;
            // Back to idle
            id(main_display).show_page(idle_page);
            id(main_display).update();
          }

# Display using MIPI SPI (CO5300 controller)
spi:
  id: display_qspi
  type: quad
  clk_pin: GPIO38
  data_pins: [GPIO4, GPIO5, GPIO6, GPIO7]

display:
  - platform: mipi_spi
    id: main_display
    model: CO5300
    cs_pin: GPIO12
    reset_pin: GPIO39
    bus_mode: quad
    update_interval: never
    dimensions:
      width: 466
      height: 466
      offset_width: 6
      offset_height: 0
    data_rate: 80MHz
    color_order: rgb
    pages:
      - id: idle_page
        lambda: |-
          it.fill(id(idle_color));
          it.image(233, 233, id(casita_idle), ImageAlign::CENTER);

      - id: blink_page
        lambda: |-
          it.fill(id(idle_color));
          it.image(233, 233, id(eyes_blink), ImageAlign::CENTER);

      - id: listening_page
        lambda: |-
          it.fill(id(listening_color));
          it.image(233, 233, id(casita_listening), ImageAlign::CENTER);

      - id: thinking_page
        lambda: |-
          it.fill(id(thinking_color));
          it.image(233, 233, id(casita_thinking), ImageAlign::CENTER);
          if (id(show_text).state) {
            it.filled_rectangle(75, 20, 210, 30, Color::WHITE);
            it.rectangle(75, 20, 210, 30, Color::BLACK);
            it.printf(75, 25, id(font_request), Color::BLACK, "%s", id(text_request).state.c_str());
          }

      - id: replying_page
        lambda: |-
          it.fill(id(replying_color));
          it.image(233, 233, id(casita_replying), ImageAlign::CENTER);
          int y_offset = 190;
          if (id(show_text).state) {
             it.filled_rectangle(75, y_offset, 210, 30, Color::WHITE);
             it.rectangle(75, y_offset, 210, 30, Color::BLACK);
             it.printf(75, y_offset + 5, id(font_response), Color::BLACK, "%s", id(text_response).state.c_str());
          }

      - id: timer_finished_page
        lambda: |-
          it.fill(id(idle_color));
          it.image(233, 233, id(casita_timer_finished), ImageAlign::CENTER);

      - id: error_page
        lambda: |-
          it.fill(id(error_color));
          it.image(233, 233, id(casita_error), ImageAlign::CENTER);

      - id: no_ha_page
        lambda: |-
          it.fill(Color::BLACK);
          it.image(233, 233, id(error_no_ha), ImageAlign::CENTER);

      - id: no_wifi_page
        lambda: |-
          it.fill(Color::BLACK);
          it.image(233, 233, id(error_no_wifi), ImageAlign::CENTER);

      - id: initializing_page
        lambda: |-
          it.fill(id(loading_color));
          it.image(233, 233, id(casita_initializing), ImageAlign::CENTER);

      - id: muted_page
        lambda: |-
          it.fill(Color::BLACK);
          it.image(233, 233, id(casita_muted), ImageAlign::CENTER);

# Touchscreen using CST9217 driver
touchscreen:
  - platform: cst9217
    display: main_display
    id: touch_input_driver
    interrupt_pin: GPIO11
    reset_pin: GPIO40
    transform:
      mirror_x: true
      mirror_y: true
    on_touch:
      - lambda: |-
          int x = touch.x;
          int y = touch.y;
          ESP_LOGI("touch", "Touch at x=%d, y=%d", x, y);
          
          // Define touch zones (divide display into 3 vertical zones)
          // Left: 0-155, Center: 156-310, Right: 311-466
          
          if (x < 155) {
            // LEFT ZONE - Previous Track
            ESP_LOGI("touch", "Left zone - Previous track");
          } 
          else if (x > 311) {
            // RIGHT ZONE - Next Track
            ESP_LOGI("touch", "Right zone - Next track");
          } 
          else {
            // CENTER ZONE - Play/Pause
            ESP_LOGI("touch", "Center zone - Play/Pause");
          }
      - if:
          condition:
            lambda: 'return touch.x < 155;'
          then:
            - homeassistant.service:
                service: media_player.media_previous_track
                data:
                  entity_id: ${external_media_player_entity}
      - if:
          condition:
            lambda: 'return touch.x > 311;'
          then:
            - homeassistant.service:
                service: media_player.media_next_track
                data:
                  entity_id: ${external_media_player_entity}
      - if:
          condition:
            lambda: 'return touch.x >= 156 && touch.x <= 310;'
          then:
            - homeassistant.service:
                service: media_player.media_play_pause
                data:
                  entity_id: ${external_media_player_entity}
      - binary_sensor.template.publish:
          id: touch_input
          state: ON
    on_release:
      then:
        - binary_sensor.template.publish:
            id: touch_input
            state: OFF

# Images - resized to 466x466 to fill the entire display
image:
  - file: ${error_illustration_file}
    id: casita_error
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${idle_illustration_file}
    id: casita_idle
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${listening_illustration_file}
    id: casita_listening
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${thinking_illustration_file}
    id: casita_thinking
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${replying_illustration_file}
    id: casita_replying
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${timer_finished_illustration_file}
    id: casita_timer_finished
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${loading_illustration_file}
    id: casita_initializing
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${mute_illustration_file}
    id: casita_muted
    resize: 466x466
    type: RGB565
    transparency: alpha_channel
  - file: ${blink_illustration_file}
    id: eyes_blink
    resize: 466x466
    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: 466x466
    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: 466x466
    type: RGB565
    transparency: alpha_channel

font:
  - 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}

text_sensor:
  - id: text_request
    platform: template
    name: "Text Request"
    on_value:
      lambda: |-
        if(id(text_request).state.length()>30) {
          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
    name: "Text Response"
    on_value:
      lambda: |-
        if(id(text_response).state.length()>30) {
          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}