SpotPear "DeepSeek Voice Chat" config

Hello community,

I recently picked up a cheap little module from Ali Express, called a “DeepSeek XiaoZhi AI Voice Chat Robot ESP32-S3 1.28 inch LCD N16R8 Development Board Astronaut Clock Desktop Ornament” for about 30 bucks CAD. It’s a nice little module, an ESP32-S3-WROOM-1 module, a 240x240 color LCD display (no touch, sadly), a max98357a DAC and speaker, and an INMP441 microphone. They’re nice little displays, and I’ve got a small config here that sets it up as a clock/weather display, it’ll show the info about the current track I’m listening to on my owntone server, and it will act as a voice assistant (similar to the S3-BOX-3).

You can find the product page here, but information is scarce. It took me quite a while to figure out the correct pinouts for all the connections, and the on-board LED eludes me still.

Here’s the config, as it currently stands. I’m still working on the on-device wake word detection, it’s not quite there yet, so the back button on the left side is currently used to trigger the voice assistant.

substitutions:
  mediaplayer: media_player.owntone_server
  current_weather: weather.my_weather_entity
  screensaver: 30min
  # Pin Connections for the SpotPear Esp32 S3 N16R8 board
  dcpin: GPIO10
  cspin: GPIO13
  clpin: GPIO14
  mopin: GPIO17
  repin: GPIO18
  bkpin: GPIO3
  btnpin: GPIO0
  mic_ws: GPIO4
  mic_sclk: GPIO5
  mic_sd: GPIO6
  spk_din: GPIO7
  spk_blck: GPIO15
  spk_lrc: GPIO16

  loading_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/loading_240_240.png
  idle_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/idle_240_240.png
  listening_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/listening_240_240.png
  thinking_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/thinking_240_240.png
  replying_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/replying_240_240.png
  error_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/error_240_240.png
  timer_finished_illustration_file: https://github.com/jptrsn/wake-word-voice-assistants/raw/main/casita/timer_finished_240_240.png

esphome:
  name: mini-clock
  friendly_name: Mini Clock
  platformio_options:
    build_flags: "-DBOARD_HAS_PSRAM"  
    board_build.arduino.memory_type: qio_opi

esp32:
  board: esp32-s3-devkitc-1
  variant: esp32s3
  flash_size: 16MB
  framework:
    type: arduino
    version: recommended
    

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "pfedbcHEI7Zbd7QXXrA/CdvmJDI8uDz5vlwj7isjQbs="

ota:
  - platform: esphome
    password: "2dfdacbb0b9b403513cbec31dcfb0616"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Mini-Clock Fallback Hotspot"
    password: "tMUPeiAXc2G3"

captive_portal:
    
spi:
  clk_pin: $clpin
  mosi_pin: $mopin

http_request:
  verify_ssl: false

i2s_audio:
  - id: speaker_i2s
    i2s_lrclk_pin: $spk_lrc
    i2s_bclk_pin: $spk_blck
  - id: mic_i2s
    i2s_lrclk_pin: $mic_ws
    i2s_bclk_pin: $mic_sclk

microphone:
  - platform: i2s_audio
    i2s_audio_id: mic_i2s
    adc_type: external
    i2s_din_pin: $mic_sd
    id: adc_mic
    pdm: false
    bits_per_sample: 16bit
    channel: left
    # on_data:
    #   - logger.log: 
    #       format: "Received %d bytes"
    #       args: ['x.size()']

media_player:
  - platform: i2s_audio
    i2s_audio_id: speaker_i2s
    dac_type: external
    i2s_dout_pin: $spk_din
    name: "ESP32 Media Player"
    mode: mono
    id: media_out

voice_assistant:
  microphone: adc_mic
  media_player: media_out
  use_wake_word: false
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  id: assist
  conversation_timeout: 30s
  on_start:
    then:
      - script.execute: show_assistant
      - lvgl.image.update:
          id: assistant_img_widget
          src: casita_initializing
  on_listening: 
    then:
      - lvgl.image.update:
          id: assistant_img_widget
          src: casita_listening
      - lvgl.label.update:
          id: text_request
          text: "..."
          hidden: true
      - lvgl.label.update:
          id: text_response
          text: "..."
          hidden: true
  on_stt_vad_end:
    then:
      - lvgl.image.update:
          id: assistant_img_widget
          src: casita_thinking
  # on_stt_end:
  #   then:
  #     - lvgl.label.update:
  #         id: text_request
  #         hidden: false
  #         text: !lambda return x;
  on_tts_start:
    then:
      - lvgl.image.update:
          id: assistant_img_widget
          src: casita_replying
      - lvgl.label.update:
          id: text_response
          hidden: false
          text: !lambda return x;
  on_tts_end:
    then:
      - lvgl.image.update:
          id: assistant_img_widget
          src: casita_idle
  on_end:
    then:
      - delay: 15s
      - media_player.stop:
          id: media_out
          announcement: true
      - script.execute: show_clock_page
      - lvgl.label.update:
          id: text_response
          hidden: true
      - lvgl.label.update:
          id: text_request
          hidden: true

# micro_wake_word:
#   vad:
#   models:
#     - model: okay_nabu
#   on_wake_word_detected:
#     then:
#       - voice_assistant.start:
#           wake_word: !lambda return wake_word;

output:
  - platform: ledc
    pin: $bkpin
    id: backlight_pwm

light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Clock Backlight"
    id: back_light
    restore_mode: ALWAYS_ON
    on_turn_off: 
      then:
        - lvgl.pause:
            show_snow: true
    on_turn_on: 
      then:
        - lvgl.resume:

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "IP Address"
  - platform: homeassistant
    id: music_state
    entity_id: $mediaplayer
  - platform: homeassistant
    id: weather_state
    entity_id: $current_weather
    on_value:
        - script.execute: update_current_weather_icon
  - platform: homeassistant
    id: track_name
    entity_id: $mediaplayer
    attribute: media_title
    on_value:
      then:
        - lvgl.label.update:
            id: track_name_label
            text: !lambda return x;
  - platform: homeassistant
    id: track_artist
    entity_id: $mediaplayer
    attribute: media_artist
    on_value:
      then:
        - lvgl.label.update:
            id: track_artist_label
            text: !lambda return x;
  - platform: homeassistant
    id: track_album
    entity_id: $mediaplayer
    attribute: media_album_name
    on_value:
      then:
        - lvgl.label.update:
            id: track_album_label
            text: !lambda return x;
            
  - platform: template
    id: outside_condition_icon
    on_value:
      then:
        - lvgl.label.update:
            id: forecast_label
            text: !lambda return x;
  - platform: homeassistant
    id: track_path
    entity_id: $mediaplayer
    attribute: entity_picture
    on_value:
      then:
        - online_image.set_url:
            id: cover_art
            url: !lambda 'return "local_ip_address_and_port_here" + id(track_path).state;'
        - component.update: cover_art              

sensor:
  - platform: homeassistant
    id: outdoor_temperature
    entity_id: $current_weather
    attribute: temperature
    on_value: 
      then:
        - lvgl.label.update:
            id: temp_widget
            text: 
              format: "%2.1f°"
              args: [ 'x' ]

binary_sensor:
  - platform: gpio
    pin: 
      number: $btnpin
      inverted: true
    id: back_button
    on_release:
      then:
        # - switch.toggle: clock_face_simple
        - voice_assistant.start
  - platform: template
    id: is_playing
    lambda: 'return id(music_state).state == "playing";'
    filters:
      - delayed_off: 2500ms
    on_press:
      then:
        - script.execute: show_music_data
    on_release:
      then:
        - script.execute: hide_music_data
    
switch:
  - platform: template
    id: clock_face_simple
    optimistic: true
    turn_on_action:
      - script.execute: show_simple_time
    turn_off_action:
      - script.execute: show_full_time

display:
  - platform: ili9xxx
    model: GC9A01A #ST7789V
    dc_pin: $dcpin
    reset_pin: $repin
    cs_pin: $cspin
    invert_colors: true
    dimensions: 
      height: 240
      width: 240
    rotation: 0

font:
  - file: "gfonts://Roboto"
    id: roboto
    size: 14
    glyphsets:
      - GF_Latin_Kernel
    glyphs: [
      "-"
    ]
  - file: "gfonts://Material+Symbols+Outlined"
    id: icons_med
    size: 24
    glyphs: [
      "\U0000e2bd", #cloud
      "\U0000e818", #foggy
      "\U0000f157", #clear day
      "\U0000f67f", #weather-hail
      "\U0000ebdb", #thunderstorm
      "\U0000f172", #partly cloudy day
      "\U0000f176", #rainy
      "\U0000f61f", #rainy-heavy
      "\U0000f61d", #rainy-snow
      "\U0000e80f", #snowing
      "\U0000e2cd", #snowy
      "\U0000e81a", #sunny
      "\U0000e29c", #airwave
      "\U0000efd8", #air
      "\U0000e51c", #darkmode
      "\U0000e002", #warning
      "\U0000eabd", #unknown med
      "\U0000e043", #shuffle
      "\U0000e040", #repeat
      "\U0000e8b5", #schedule
    ]
  - file: "gfonts://Dancing+Script"
    id: dancing_script
    size: 32
    glyphs: ["0123456789"]

online_image:
  - id: cover_art
    format: jpeg
    url: "http://local_ip_address_and_port_here/api/media_player_proxy/media_player.owntone_server"
    resize: 240x240
    type: RGB565
    on_download_finished:
      then:
        - lvgl.image.update:
            id: cover_art_widget
            src: cover_art
        - lvgl.widget.show:
            id: cover_art_widget
    on_error:
      then:
        - online_image.release: cover_art
        - lvgl.widget.hide:
            id: cover_art_widget

image:
  - file: $loading_illustration_file
    type: RGB565
    id: casita_initializing
    resize: 240x240
  - file: $idle_illustration_file
    type: RGB565
    id: casita_idle
    resize: 240x240
  - file: $listening_illustration_file
    type: RGB565
    id: casita_listening
    resize: 240x240
  - file: $thinking_illustration_file
    type: RGB565
    id: casita_thinking
    resize: 240x240
  - file: $replying_illustration_file
    type: RGB565
    id: casita_replying
    resize: 240x240
  - file: $error_illustration_file
    type: RGB565
    id: casita_error
    resize: 240x240
  - file: $timer_finished_illustration_file
    type: RGB565
    id: casita_timer_finished
    resize: 240x240

time:
  - platform: sntp
    timezone: "America/Toronto"
    id: esptime
    on_time_sync: 
      then:
        - script.execute: update_date
        - script.execute: update_hours
        - script.execute: update_minutes
        - script.execute: update_seconds
        - lvgl.widget.show: clock_hands
    on_time:
      - cron: '* * * * * *' # every second
        then:
          - script.execute: update_seconds
      - seconds: 0 # every minute at zero seconds
        then:
          - script.execute: update_minutes
      - minutes: 0 # every hour at zero minutes
        then:
          - script.execute: update_hours
          - script.execute: update_date

script:
  - id: update_seconds
    then:
      - lvgl.indicator.update:
          id: second_hand
          value: !lambda |-
            return id(esptime).now().second;
  - id: update_minutes
    then:
      - lvgl.indicator.update:
          id: minute_hand
          value: !lambda |-
            return id(esptime).now().minute;
  - id: update_hours
    then:
      - lvgl.indicator.update:
          id: hour_hand
          value: !lambda |-
            auto now = id(esptime).now();
            return std::fmod(now.hour, 12) * 60 + now.minute;
  - id: update_date
    then:
      - lvgl.label.update:
          id: date_label
          text: 
            time_format: "%b %d"
            time: esptime
      - lvgl.label.update:
          id: day_label
          text:
            time_format: "%a"
            time: esptime
  - id: show_simple_time
    then:
      - light.dim_relative:
          id: back_light
          relative_brightness: -75%
          transition_length: 1s
      - delay: 1s
      - lvgl.widget.hide: clock_face
      - lvgl.widget.hide: day_label
      - lvgl.widget.hide: date_label
      - lvgl.widget.hide: forecast_label
      - lvgl.widget.hide: temp_widget
  - id: show_full_time
    then:
      - script.stop: show_simple_time
      - lvgl.widget.show: clock_face
      - lvgl.widget.show: day_label
      - lvgl.widget.show: date_label
      - lvgl.widget.show: forecast_label
      - lvgl.widget.show: temp_widget
      - light.control:
          id: back_light
          brightness: 100%
          state: on
          transition_length: 1s
  - id: update_current_weather_icon
    then:
      - lambda: |-
          if (id(weather_state).state == "clear-night") {
            id(outside_condition_icon).publish_state("\U0000e51c");
          } else if (id(weather_state).state == "cloudy") {
            id(outside_condition_icon).publish_state("\U0000e2bd");
          } else if (id(weather_state).state == "fog") {
            id(outside_condition_icon).publish_state("\U0000e818");
          } else if (id(weather_state).state == "hail") {
            id(outside_condition_icon).publish_state("\U0000f67f");
          } else if (id(weather_state).state == "lightning") {
            id(outside_condition_icon).publish_state("\U0000ebdb");
          } else if (id(weather_state).state == "lightning-rainy") {
            id(outside_condition_icon).publish_state("\U0000ebdb");
          } else if (id(weather_state).state == "partlycloudy") {
            id(outside_condition_icon).publish_state("\U0000f172");
          } else if (id(weather_state).state == "pouring") {
            id(outside_condition_icon).publish_state("\U0000f61f");
          } else if (id(weather_state).state == "rainy") {
            id(outside_condition_icon).publish_state("\U0000f176");
          } else if (id(weather_state).state == "snowy") {
            id(outside_condition_icon).publish_state("\U0000e2cd");
          } else if (id(weather_state).state == "snowy-rainy") {
            id(outside_condition_icon).publish_state("\U0000f61d");
          } else if (id(weather_state).state == "sunny") {
            id(outside_condition_icon).publish_state("\U0000e81a");
          } else if (id(weather_state).state == "windy") {
            id(outside_condition_icon).publish_state("\U0000e29c");
          } else if (id(weather_state).state == "windy-variant") {
            id(outside_condition_icon).publish_state("\efd8");
          } else if (id(weather_state).state == "exceptional") {
            id(outside_condition_icon).publish_state("\U0000e002");
          } else {
            id(outside_condition_icon).publish_state("");
          }
  - id: show_clock_page
    then:
      - lvgl.page.show:
          id: clock_page
          time: 250ms
          animation: "FADE_OUT"
  - id: show_music_data
    then:
      - lvgl.page.show:
          id: music_page
          time: 250ms
          animation: "FADE_OUT"
  - id: hide_music_data
    then:
      - script.execute: show_clock_page
      - online_image.release: cover_art
  - id: show_assistant
    then:
      - lvgl.page.show:
          id: assistant_page
          time: 250ms
          animation: "OVER_LEFT"

lvgl:
  buffer_size: 25%
  height: 240
  width: 240
  bg_color: 0x000000
  style_definitions:
    - id: date_style
      text_font: unscii_8
      align: center
      text_color: 0xD0D0D0
      bg_color: 0x000000
      bg_opa: 25%
      radius: 4
      pad_all: 2
    - id: icon_style
      text_font: icons_med
      align: center
      text_color: 0x707070
    - id: clock_data
      text_font: roboto
      align: center
      text_color: 0xA0A0A0
      radius: 12
      pad_all: 3
      bg_opa: 25%
      bg_color: 0x000000
    - id: clock_media
      text_font: roboto
      text_color: 0xFFFFFF
      bg_color: 0x000000
      pad_all: 6
      radius: 18
      bg_opa: 75%

  pages:
    - id: clock_page
      bg_color: 0x000000
      width: 240
      height: 240
      scrollbar_mode: "OFF"
      widgets:
        - obj:
            id: clock_face
            bg_color: 0x000000
            border_width: 0
            align: CENTER
            width: 240
            height: 240
            scrollbar_mode: "OFF"
            widgets:
              - meter: #clock face
                  id: clock_face_fancy
                  height: 240
                  width: 240
                  align: CENTER
                  bg_opa: TRANSP
                  border_width: 0
                  text_color: 0xD00000
                  text_font: dancing_script
                  scales:
                    - range_from: 0 # minutes scale
                      range_to: 60
                      angle_range: 360
                      rotation: 270
                      ticks:
                        width: 2
                        count: 61
                        length: 2
                        color: 0x0A0A0A
                    - range_from: 0 # hours scale for labels
                      range_to: 11
                      angle_range: 330
                      rotation: 270
                      ticks:
                        width: 2
                        count: 12
                        length: 2
                        major:
                          stride: 3
                          width: 0
                          length: 0
                          color: 0x5F0606
                          label_gap: 3
        - meter: # clock hands
            height: 240
            width: 240
            id: clock_hands
            hidden: true
            align: CENTER
            bg_opa: TRANSP
            border_width: 0
            text_color: 0xFFFFFF
            scales:
              - range_from: 0 # minutes scale
                range_to: 60
                angle_range: 360
                rotation: 270
                ticks:
                  count: 0
                indicators:
                  - line:
                      id: minute_hand
                      width: 3
                      color: 0xA0A0A0
                      r_mod: -20
                      value: 0
                  - line:
                      id: second_hand
                      width: 2
                      color: 0xff1000
                      r_mod: -12
              - range_from: 0 # hi-res hours scale for hand
                range_to: 720
                angle_range: 360
                rotation: 270
                ticks:
                  count: 0
                indicators:
                  - line:
                      id: hour_hand
                      width: 5
                      color: 0xa6a6a6
                      r_mod: -45
                      value: 0
        - label:
            styles: date_style
            id: day_label
            y: 52
        - label:
            id: date_label
            styles: date_style
            y: 65
        - label:
            align: CENTER
            id: temp_widget
            y: 32
            x: -40
            styles: clock_data
            text: "---"
        - label:
            align: CENTER
            id: forecast_label
            x: 45
            y: 32
            styles: icon_style
            text: "\U0000eabd"
        # - spinner:
        #     id: loading_spinner
        #     arc_length: 36
        #     arc_rounded: true
        #     spin_time: 2s
        #     align: CENTER
    - id: music_page
      bg_color: 0x000000
      width: 240
      height: 240
      scrollbar_mode: "OFF"
      widgets:
        - image:
            align: CENTER
            src: cover_art
            id: cover_art_widget
            antialias: true
            hidden: true
            width: 240
        - obj:
            align: CENTER
            width: 240
            height: 240
            pad_all: 3
            bg_opa: transp
            border_opa: transp
            layout:
              type: FLEX
              flex_flow: COLUMN
              flex_align_main: CENTER
              flex_align_cross: CENTER
              flex_align_track: CENTER
            widgets:
              - label:
                  styles: clock_media
                  id: track_name_label
              - label:
                  styles: clock_media
                  id: track_artist_label
              - label:
                  styles: clock_media
                  id: track_album_label
    - id: assistant_page
      bg_color: 0x000000
      width: 240
      height: 240
      scrollbar_mode: "OFF"
      widgets:
        - image:
            align: CENTER
            src: casita_idle
            id: assistant_img_widget
            width: 240
            height: 240
            scrollbar_mode: "OFF"
        - label:
            styles: clock_media
            id: text_request
            align: BOTTOM_MID
            y: -55
        - label:
            styles: clock_media
            id: text_response
            align: BOTTOM_MID
            y: -35
1 Like

Adding it here would be great https://devices.esphome.io/

Does yours have a V4 sticker on the bottom? I am curious if mine is a different configuration hardware wise than yours. Your config doesn’t work on mine OOTB. Not a complaint ofc

I’m not sure if it had that sticker, I removed them when the device first arrived. You can use a little heat on the adhesive around the edge of the screen to open it up, mine has this PCB inside.


I would recommend pruning everything except the hardware config, and test if the display shows the LVGL test screen when no pages are defined. That would help you diagnose if it’s different hardware or just a difference in our respective home assistant environments.

3 Likes

I have also purchased one of these, and managed to get the LED working, its a single WS2812 on GPIO48

The schematic was really helpful for me to identify the pins, as I hadn’t found your post yet: http://cdn.static.spotpear.com/uploads/picture/learn/ESP32/ESP32-S3-1.28inch-AI/ESP32S3-1.28inch-AI.pdf

1 Like

Hey @ptrsnja i got you code working on a “V5 EN” version of this device. Display, Mic and buttons are working, but no audio. According to chatGPT this device uses an * ES8311. Any idea how to fix this? Thank you very much!

Here is the section of my config for the microphone.

i2s_audio:
  - id: speaker_i2s
    i2s_lrclk_pin: $spk_lrc
    i2s_bclk_pin: $spk_blck
  - id: mic_i2s
    i2s_lrclk_pin: $mic_ws
    i2s_bclk_pin: $mic_sclk

microphone:
  - platform: i2s_audio
    i2s_audio_id: mic_i2s
    adc_type: external
    i2s_din_pin: $mic_sd
    id: adc_mic
    pdm: false
    bits_per_sample: 16bit
    channel: left
    # on_data:
    #   - logger.log: 
    #       format: "Received %d bytes"
    #       args: ['x.size()']

I would be incredibly wary of anything any LLM tells you about these devices. Looking at the PCB, I can tell that mine has an INMP441 just based on the footprint of the module, which matches the INMP441 that are sold as a standalone breakout board.

If that doesn’t work, try posting a snippet of your config, as well as what you see in the logs when you try to trigger the microphone

that is exactly what it is, a INMP441 mic, and a MAX98357 amp, you can duplicate this little round gadget with a standard esp32-s3 + those components and same code will run.
this little cube isn’t among the official esp32 builds here:

but the sourcecode is available here and you need to compile it to change wake word :slight_smile:

warning, if you flash official code for esp32 display will not work.

pins used for display is listed at the bottom of above page (2nd link) and the rest of the pins is standard as per project. (first link and shown in source code)

somehow it didn’t post the link for source code (2nd link)

Hey @ptrsnja thanks again for the great support! I followed your recommendation and took apart the XiaoThi device — the PCB looks identical to yours.

However, no matter what I try, I can’t get the speaker to output anything. I used your code as well as some ESP-IDF samples with wake-word support enabled, but still no audio.

I’m fairly certain the hardware is fine — when I first powered it on, I briefly heard a voice prompt from the stock firmware.

Any ideas what might be wrong?

Here’s my current audio config:

i2s_audio:
  - id: i2s # For microphone
    i2s_lrclk_pin: GPIO4 #WS 
    i2s_bclk_pin: GPIO5  #SCK
  - id: speaker_i2s # For speaker
    i2s_lrclk_pin: GPIO16
    i2s_bclk_pin: GPIO15

microphone:
  - platform: i2s_audio
    i2s_audio_id: i2s
    adc_type: external
    i2s_din_pin: GPIO6 #SD
    id: va_mic
    channel: left
    pdm: false
    bits_per_sample: 16bit

speaker:
    platform: i2s_audio
    id: va_speaker
    i2s_audio_id: speaker_i2s
    dac_type: external
    i2s_dout_pin:   
      number: GPIO7 # DIN Pin of the MAX98357A Audio Amplifier
    channel: mono
    #bits_per_sample: 32bit
    #sample_rate: 16000

Looks like your hardware configuration is correct, so I’m more suspicious of the service call at this point. For the next steps, I would do the following:

  1. Add a media_player component to your yaml so that it can be called from home assistant (I’ll add my config after this list)
  2. After uploading the code, connect the device over usb to your computer
  3. Visit https://web.esphome.com, connect to the device, and open the logs viewer
  4. From home assistant, open the developer tools and navigate to the Actions tab
  5. Select the Text-to-speech (TTS): Speak action, select Piper as the target, and select your device’s media player as the Media player entity
  6. Write a small piece of text, and then click the Perform Action button
  7. Go back to the log viewer, and check that it says it’s announcing the audio, and listen for it to come out of the speaker.

Here’s my media player config:

media_player:
  - platform: i2s_audio
    i2s_audio_id: speaker_i2s
    dac_type: external
    i2s_dout_pin: $spk_din
    name: "ESP32 Media Player"
    mode: mono
    id: media_out

This process works for my device, and the logs look like this:

[12:04:38][D][media_player:074]: 'ESP32 Media Player' - Setting
[12:04:38][D][media_player:081]:   Media URL: http://192.168.1.12:8123/api/tts_proxy/eqSu3IiTUVtqT0L2IbYJuA.mp3
[12:04:38][D][media_player:087]:  Announcement: yes
[12:04:38][W][component:239]: Component i2s_audio.media_player took a long time for an operation (527 ms).
[12:04:38][W][component:240]: Components should block for at most 30 ms.

Hello everyone,

How’s the performance of these devices? Are they a good replacement for Alexa?

And does the microphone catches well wake up words?

Thank you!

when you buy one it comes with xiaozhi AI on it and cannot control stuff, but super fun to talk with, can be used as a teacher for the kids, a helper in the kitchen (step by step how to boil an egg?) and stuff like that, buy one and play with it so you understand what it is.

here people are talking about putting alternative code for esphome so they can use it as a standard home assistant voice assist.

the mic is a standard INMP441 and it’s not bad, but also not as great as alexa and google sattelites and do not work well if tv or music is playing. but a fun gadget for almost no money.

personally i would never buy alexa, and never use my old google sattelites, but these little esp’s with xiaozhi ai are FUN and gives you and idea about where voice assistants are today. alexa is old tech :-p

1 Like

When i flash with firmware like image, result is OK

but when i build and flash with board type : 面包板新版接线(WiFi)+ LCD,
no display

i changed pinout of SPI but nothing happens, LCD not show :frowning:

what’s pin of SPI? i try SPI pins

but LCD not display


When i flash this bin, my device is completely disabled, can’t load bin or anything else

i see a small issue here, the mic side of the INMP is on the other side, meaning it’s facing down to the system board, it works but i think i would have placed it differently, like upright with the mic hole close to the hole in enclosure…

If you want a solid choice for voice control, I would only recommend the Home Assistant Voice Preview Edition.