Far field satellite with an Elegant 3d printed enclosures

That’s the thread for the original korvo that has a vanilla ESP32 in it. It’s the around 20 dollar one on AliExpress. I actually just posted the below because it shouldn’t work at all without an S3 with PSRAM. The GPIO pins are different between the 2.models so I’m surprised it worked at all but explains the odd behavior… Also, there are a mix of korvov1.1 and korvo-1 posts that just make it a bit more confusing to navigate through.


the voice pipeline used should have no value in home assistant because it’s defined in the ESPHome configuration file. While I have been working on the Korvo-1, and I know this isn’t the thread for that one, I’m just curious is PSRAM is no longer required for microwakeword because everything I’ve read says it’s a requirement. Additionally the ESPHome documation says it’s still required although it may not have been updated.

The micro_wake_word component requires an ESP32-S3 with PSRAM to function

I do have the korvo-1 working with microwakeword but I’m having an “odd” issue that I want to get fixed because the firmware doesn’t show and ESPHome thinks the korvo-1 is offline and prompts me to install the “newly” found one. It works with zero issues which is just really odd. In ESPHome it shows up as red (offline) but if I click on logs they show up and I can see everything working, yet ESPHome still thinks it’s offline. Very odd. I will post the confirmation once I figure out what’s going on because you will more than likely end up with the same issue. I also used the below so if the GPIO pins don’t match what’s in that other thread then it’s not going to work. Look at the file with pins in the name.

here is the yaml, everything works without any issues. I tried looking into the firmware thing and it looks like it has to be added on the esphome side and there has been a feature request put in. If it will ever happen is another story though. I did get some warnings about the microphone and during the tensorflow.

Also, it was actually working with OpenWakeWord still until I looked into it. I realized after looking into the repository I am pointing to and if has an option to work with both. When looking more closely at the logs, it wasn’t, they are totally different (as far a cli output) when being triggered and processing a wake word. Remember to create a new pipline with no wake word defined. If you use your default, I believe it will use OpenWakeWord. Another fun thing is since I set the board to esp32-s3-devkit-1, it formatted the ROM at 4MB or either 8MB. It was saying 19344 bytes was to large for my flash of 18345 bytes, which I interpreted as 19MB and 18MB which is obviously WAY off. I probably spent a good 2 hours trying to figure this out when I noticed that when I got that message the PSRAM was more than double the size!!!. So all I had to do was add one simple line to specify flash size as 16MB. DOH. Oh well, it’s working, that is all that matters.

Remember to add the API key below and change wifi if not using the same secret format I am using also. For some reason, a few releases ago all the LED’s stopped working. I don’t know if if it’s just a weird LED thing or if only one mic is working. I was pretty much relying on that repo for a lot of stuff because he had the actual espressif files with the includes to the driver files and specific hardware firmware. Stuff like that. So, you could just specify the mic and one of the mic gpio’'s, but I doubt it will work out as well as this because it won’t be utilizing all the hardware. I honestly can’t say if all of it is working with this either. The 3.5mm output works fine, haven’t tried the speaker output and all my speakers are passive, I also don’t have a jst adapter that will fit it either.

Just remember, you may forever have a new entity wanting to be adopted after this, just leave it alone, even though it shows up as red it just works and outside someone adding the firmware to esphome, it will always be that way. What is really odd is there is a line in there that points to a github url that doesn’t exist for the firmware. If I comment it out, flashing fails, so it has to be in there, even though the the file doesn’t exist in the main esphome repository it’s pointed to :roll_eyes:

Lastly, it took over 600 seconds to compile, and I am running HA on a 7 year old intel Nuc with an I5 so if you are using a raspberry pi 4, it could take a while longer so make sure to just wait until it flashes or times out. Actually , if it’s your first time flashing it you will have to plug it into a PC or the HA server so you will see the CLI output. The tensorflow part takes quite some time. You have to hold the boot button when plugging the power cord in to put it into boot mode. The USB cable for for communication should already be plugged into your PC when doing this.

substitutions:
  name: "korvo"
  friendly_name: korvo

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  name_add_mac_suffix: true
  platformio_options:
    board_build.flash_mode: dio
    upload_speed: 460800
  project:
    name: esphome.voice-assistant
    version: "1.0"
  min_version: 2023.11.5
  on_boot:
    - priority: 600
      then:
        - light.turn_on:
            id: led_ring
            brightness: 70%
            effect: connecting

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_AUDIO_BOARD_CUSTOM: "y"
      CONFIG_ESP32_S3_KORVO1_BOARD: "y"
    components:
      - name: esp32_s3_korvo1_board
        source: github://abmantis/esphome_custom_audio_boards@main
        refresh: 0s

psram:
  mode: octal
  speed: 80MHz

external_components:
  - source: github://pr#5230
    components: esp_adf
    refresh: 0s

ota:
logger:
api:
  encryption:
     key: you_api_key
  on_client_connected:
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - delay: 1s
            - voice_assistant.start_continuous:
            - delay: 1s
            - voice_assistant.stop:
            - delay: 1500ms
            - voice_assistant.start_continuous:
            - script.execute: reset_led
  on_client_disconnected:
    then:
      - light.turn_on:
          id: led_ring
          blue: 0%
          red: 100%
          green: 100%
          brightness: 50%
          effect: connecting

dashboard_import:
  package_import_url: github://esphome/firmware/voice-assistant/esp32-s3-korvo1.yaml@main

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: 192.168.0.xx
  ap:
  on_connect:
    then:
      - delay: 5s # Gives time for improv results to be transmitted
      - ble.disable:
  on_disconnect:
    then:
      - ble.enable:

improv_serial:

esp32_improv:
  authorizer: none

button:
  - platform: factory_reset
    id: factory_reset_btn
    name: Factory reset

esp_adf:
  board: esp32s3korvo1

microphone:
  - platform: esp_adf
    id: korvo_mic

speaker:
  - platform: esp_adf
    id: korvo_speaker
    
micro_wake_word:
  on_wake_word_detected:
    # then:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;
    - light.turn_on:
        id: led_ring      
        red: 30%
        green: 30%
        blue: 70%
        brightness: 60%
        effect: fast pulse 
  model: okay_nabu.

voice_assistant:
  id: voice_asst
  microphone: korvo_mic
  speaker: korvo_speaker
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2
  use_wake_word: true
  on_listening:
    - light.turn_on:
        id: led_ring
        blue: 100%
        red: 0%
        green: 0%
        brightness: 100%
        effect: wakeword
  on_tts_start:
    - light.turn_on:
        id: led_ring
        blue: 0%
        red: 0%
        green: 100%
        brightness: 50%
        effect: pulse
  on_end:
    - delay: 500ms
    - wait_until:
        not:
          speaker.is_playing:
    - script.execute: reset_led
  on_error:
    - light.turn_on:
        id: led_ring
        blue: 0%
        red: 100%
        green: 0%
        brightness: 100%
        effect: none
    - delay: 1s
    - script.execute: reset_led
    - script.wait: reset_led
    - lambda: |-
        if (code == "wake-provider-missing" || code == "wake-engine-missing") {
          id(use_wake_word).turn_off();
        }

script:
  - id: reset_led
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - light.turn_on:
                id: led_ring
                blue: 100%
                red: 0%
                green: 0%
                brightness: 30%
                effect: none
          else:
            - light.turn_off: led_ring

switch:
  - platform: gpio
    id: pa_ctrl
    pin: GPIO38
    name: "${friendly_name} Speaker Mute"
    restore_mode: ALWAYS_ON

  - platform: template
    name: Use wake word
    id: use_wake_word
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    entity_category: config
    on_turn_on:
      - lambda: id(voice_asst).set_use_wake_word(true);
      - if:
          condition:
            not:
              - voice_assistant.is_running
          then:
            - voice_assistant.start_continuous
      - script.execute: reset_led
    on_turn_off:
      - voice_assistant.stop
      - script.execute: reset_led

light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO19
    num_leds: 12
    rmt_channel: 0
    rgb_order: GRB
    chipset: ws2812
    default_transition_length: 0s
    effects:
      - pulse:
          name: "Pulse"
          transition_length: 0.5s
          update_interval: 0.5s
      - addressable_twinkle:
          name: "Working"
          twinkle_probability: 5%
          progress_interval: 4ms
      - addressable_color_wipe:
          name: "Wakeword"
          colors:
            - red: 0%
              green: 50%
              blue: 0%
              num_leds: 12
          add_led_interval: 20ms
          reverse: false
      - addressable_color_wipe:
          name: "Connecting"
          colors:
            - red: 60%
              green: 60%
              blue: 60%
              num_leds: 12
            - red: 60%
              green: 60%
              blue: 0%
              num_leds: 12
          add_led_interval: 100ms
          reverse: true

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
  - platform: template
    name: "${friendly_name} Set"
    id: btn_set
  - platform: template
    name: "${friendly_name} Play"
    id: btn_play
  - platform: template
    name: "${friendly_name} Mode"
    id: btn_mode
    on_multi_click:
      - timing:
          - ON for at least 10s
        then:
          - button.press: factory_reset_btn
  - platform: template
    name: "${friendly_name} Record"
    id: btn_record
    on_press:
      - voice_assistant.start:
      - light.turn_on:
          id: led_ring
          brightness: 100%
          effect: "Wakeword"
    on_release:
      - voice_assistant.stop:
      - light.turn_off:
          id: led_ring

sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 8
    attenuation: 11db
    update_interval: 15ms
    filters:
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 1
      - delta: 0.1
    on_value_range:
      - below: 0.55
        then:
          - binary_sensor.template.publish:
              id: btn_volume_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_volume_down
              state: ON
      - above: 1.02
        below: 1.33
        then:
          - binary_sensor.template.publish:
              id: btn_set
              state: ON
      - above: 1.43
        below: 1.77
        then:
          - binary_sensor.template.publish:
              id: btn_play
              state: ON
      - above: 1.87
        below: 2.15
        then:
          - binary_sensor.template.publish:
              id: btn_mode
              state: ON
      - above: 2.25
        below: 2.56
        then:
          - binary_sensor.template.publish:
              id: btn_record
              state: ON
      - above: 2.8
        then:
          - binary_sensor.template.publish:
              id: btn_volume_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_volume_down
              state: OFF
          - binary_sensor.template.publish:
              id: btn_set
              state: OFF
          - binary_sensor.template.publish:
              id: btn_play
              state: OFF
          - binary_sensor.template.publish:
              id: btn_mode
              state: OFF
          - binary_sensor.template.publish:
              id: btn_record
              state: OFF
1 Like

Sorry for the quietness on this thread, I’ve been spending my time trying to develop a cheap smart speaker solution that hopefully ticks the boxes for the most users possible.

I’m guessing the code above is for the Korvo1 V2.4 not the Korvo V1.1 (that naming convention caught me out too, Now I have 3 ESP32 based units that don’t work with ESPHome :sweat_smile:) though I did have some luck with the ESP32-LyraTD-MSC, despite it having a lower end cpu, happily picked up the wake word, and even worked as an input and output for assist, until I tried to transplant a ESP32-S3 N16R8 and it got locked into bootloader mode.

1 Like

Yes, it’s for the Korvo-1. Real great naming convention. It’s the one with an S3 with 16MB of ROM and 8MB of PSRAM so not the Korvov1.1 or whatever, technically mine is korvo-1 v5, and there is a Korvo-2 also which has screen and camera interface along with 2 mics and a speaker if I am not mistaken. If you use the code above it that I originally posted it will work with OpenWakeWord, just take out the microwakeword part. from “on_wake_word_detected:” to “model”, just delete all that and it will work on the KorvoV1.1 using OpenWakeWord. Microwakeword requires PSRAM and the vanilla ESP32 in that model doesn’t have any. I originally thought it was using microwakeword, it wasn’t, it was using Open. Works better with micro, just noticeably quicker to allow a voice command but still works well with Open. If you do use micro, create a voice pipline with no wakeword. I have heard conflicting information about if you use a pipeline with a wake word defined it uses Open.

Another ethe above won’t work for the korvov1.1 . It’s getting the GPIO.amdnother information from that repo. What you would need is something similar to below, which I’m unsure how I missed during my searches. Issue is it’s for the same model I have so the GPIO pins and drivers/code s defined may be different. Someone should be able to redu the GPIO pins, and their has to be some v1.1 yaml out there. I’ll do some searches but something like the ESPHome.code in the below link, which doesn’t depend on any repositories, is more straight forward. I actually might see how the below compares to the above but I’ve been impressed in the difference in picking up the trigger word much easier with micro.

EDIT: Specific model just to make sure there is no confusion

1 Like

Okay, last post, ESPHome yaml for korvo V1. 1. Obviously only works with Openeakeword. Also, both the Korvo-1 and Korvo V1.1 use the same mic hat. I believe there are a few sellers on AliExpress that sell just the korvo-1 unit for cheaper, you can use your existing one for the V1 1 on the -1 with the S3. Just an FYI.

SOURCE: Voice Assistant-Add support for the espressif esp32-korvo-v1.1 · Issue #2430 · esphome/feature-requests · GitHub

This is for the Korvo V1.1, apparently working with microwakeword. The V1. 1 has PSRAM but it’s external, on the S3 the PSRAM is actually on the chip. I have my doubts about microwakeword working due to the entire PSRAM portion being commented out as it’s required per devs for it to work. Regardless, it works one way or the other. I don’t have the V1.1 to test. Audio out on 3.5mm doesn’t work though. Just be aware of that. They say they almost have it working. Apparently handles it in.an odd way.

From ESP32-Korvo V1.1 user manual:
ESP32-WROVER-E: This ESP32 module contains the latest ESP32-D0WD-V3, a 16 MB flash and a 8 MB PSRAM for flexible data storage, featuring Wi-Fi / BT connectivity and data processing capability.

substitutions:
  name: esp32-korvo-1
  friendly_name: esp32-korvo-1
  voice_assist_idle_phase_id: "1"
  voice_assist_listening_phase_id: "2"
  voice_assist_thinking_phase_id: "3"
  voice_assist_replying_phase_id: "4"
  voice_assist_not_ready_phase_id: "10"
  voice_assist_error_phase_id: "11"
  voice_assist_muted_phase_id: "12"
  micro_wake_word_model: okay_nabu
esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2023.12.8
  platformio_options:
    board_build.flash_mode: dio
  project:
    name: esphome.voice-assistant
    version: "2.0"
  on_boot:
    - priority: -100
      then:
        - light.turn_on:
            id: led_ring
            blue: 0%
            red: 100%
            green: 0%
            effect: Fast Pulse
        - delay: 1s
        - wait_until:
            condition:
              wifi.connected:
        - light.turn_on:
            id: led_ring
            blue: 0%
            red: 100%
            green: 50%
            effect: Slow Pulse
        - wait_until: 
            condition:
              api.connected
        - lambda: id(init_in_progress) = false;
        - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        - script.execute: reset_led
esp32:
  board: esp-wrover-kit
  flash_size: 16MB
  framework:
    type: esp-idf
    version: recommended
    sdkconfig_options:
      CONFIG_IDF_TARGET_ESP32: y
      CONFIG_ESPTOOLPY_FLASHMODE_QIO: y
      CONFIG_ESPTOOLPY_FLASHFREQ_80M: y
      CONFIG_ESPTOOLPY_FLASHSIZE_16MB: y
      CONFIG_PARTITION_TABLE_CUSTOM: y
      CONFIG_PARTITION_TABLE_CUSTOM_FILENAME: "default_16MB.csv" #"partitions_esp32.csv"
      CONFIG_PARTITION_TABLE_FILENAME: "default_16MB.csv" #"partitions_esp32.csv"
      CONFIG_PARTITION_TABLE_OFFSET: "0x8000"
      CONFIG_ESP32_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32_SPIRAM_SUPPORT: y
      CONFIG_SPIRAM_SPEED_80M: y
      CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT: y
      CONFIG_I2S_ENABLE_DEBUG_LOG: y
#psram:
#  mode: octal
#  speed: 80MHz
external_components:
  - source: github://rpatel3001/esphome@es8311
    components: [ es8311 ]
  - source: github://rpatel3001/esphome@es7210
    components: [ es7210 ]
  - source: github://pr#5230
    components:
      - esp_adf

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "vRvf5APYhFeBjsFt8zzQ6xpuiZqn3oCAIbyVHCBawWM="

ota:
  password: "9522b9fe61f659e429743438edf3240e"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-Korvo-1 Fallback Hotspot"
    password: "vBJEmQ5iJHQx"

captive_portal:

i2c:
  - id: bus
    sda: GPIO19
    scl: GPIO32
    scan: true
    frequency: 400kHz

es8311:
  address: 0x18

es7210:
  address: 0x40

output:
  - platform: gpio
    id: pa_ctrl
    pin:
      number: GPIO12
      ignore_strapping_warning: true
i2s_audio:
  - id: codec
    i2s_lrclk_pin: GPIO22 
    i2s_bclk_pin: GPIO25 
    i2s_mclk_pin:
       number: GPIO0
       allow_other_uses: true
       ignore_strapping_warning: true
  - id: mic_adc
    i2s_lrclk_pin: GPIO26 
    i2s_bclk_pin: GPIO27 
    i2s_mclk_pin:
       number: GPIO0
       allow_other_uses: true
       ignore_strapping_warning: true

esp_adf:

speaker:
  - platform: i2s_audio
    id: external_speaker
    dac_type: external
    i2s_audio_id: codec
    i2s_dout_pin: GPIO13
    mode: mono

microphone:
  - platform: i2s_audio
    id: external_mic
    adc_type: external
    i2s_audio_id: mic_adc
    i2s_din_pin: GPIO36
    pdm: false


micro_wake_word:
  model: ${micro_wake_word_model}  #okay_nabu
  on_wake_word_detected:
    then:
      - voice_assistant.start:
          wake_word: !lambda return wake_word;

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 2
  auto_gain: 15dBFS
  volume_multiplier: 0.5

  on_listening:
    - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
    - script.execute: reset_led
  on_stt_vad_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
    - script.execute: reset_led
  on_tts_start:
    - light.turn_on:
        id: led_ring
        blue: 0%
        red: 100%
        green: 100%
        brightness: 60%
        effect: Working
  on_stt_end: 
    - homeassistant.service:
        service: media_player.play_media
        data:
          entity_id: media_player.ke_ting
          media_content_id: !lambda return x;
          media_content_type: music
          announce: "true"

  on_tts_stream_start:
    - output.turn_on: pa_ctrl
    - delay: 100ms
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - script.execute: reset_led

  on_end:
    - wait_until:
        not:
          speaker.is_playing:
    - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
    - script.execute: reset_led
    - if:
        condition:
          and:
            - switch.is_off: mute
            - lambda: return id(wake_word_engine_location).state == "On device";
        then:
          - wait_until:
              not:
                voice_assistant.is_running:
          - micro_wake_word.start:

  on_error:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:
          - lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};
          - script.execute: reset_led
          - delay: 2s
          - 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: reset_led

  on_client_connected:
    - if:
        condition:
          switch.is_off: mute
        then:
          - if:
              condition:
                lambda: return id(wake_word_engine_location).state == "In Home Assistant";
              then:
                - lambda: id(voice_asst).set_use_wake_word(true);
                - voice_assistant.start_continuous:
          - if:
              condition:
                lambda: return id(wake_word_engine_location).state == "On device";
              then:
                - micro_wake_word.start
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false;
    - script.execute: reset_led

  on_client_disconnected:
    - if:
        condition:
          lambda: return id(wake_word_engine_location).state == "In Home Assistant";
        then:
          - lambda: id(voice_asst).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
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
    - script.execute: reset_led


script:
  - id: reset_led
    then:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_listening_phase_id};
                then:                     
                  - light.turn_on:
                      id: led_ring
                      blue: 0%
                      red: 0%
                      green: 100%
                      brightness: 100%
                      effect: wakeword
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_thinking_phase_id};
                then:                     
                  - light.turn_on:
                      id: led_ring
                      blue: 100%
                      red: 100%
                      green: 0%
                      brightness: 100%
                      effect: Working
                  - delay: 100ms
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_replying_phase_id};
                then:                     
                  - light.turn_on:
                      id: led_ring
                      blue: 100%
                      red: 0%
                      green: 0%
                      brightness: 100%
                      effect: Working
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_idle_phase_id};
                then:
                  - light.turn_on:
                      id: led_ring
                      blue: 100%
                      red: 0%
                      green: 0%
                      brightness: 40%
                      effect: none
                  - delay: 200ms
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_not_ready_phase_id};
                then:                     
                  - light.turn_on:
                      id: led_ring
                      blue: 40%
                      red: 100%
                      green: 0%
                      effect: Slow Pulse
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_error_phase_id};
                then:                     
                  - light.turn_on:
                      id: led_ring
                      blue: 0%
                      red: 100%
                      green: 0%
                      brightness: 100%
                      effect: none
            - if:
                condition:
                  lambda: return id(voice_assistant_phase) == ${voice_assist_muted_phase_id};
                then:                     
                  - light.turn_off: led_ring
          else:
            - light.turn_on:
                id: led_ring
                blue: 0%
                red: 100%
                green: 0%
                effect: Fast Pulse

light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO33 #GPIO19
    num_leds: 12
    rmt_channel: 0
    rgb_order: GRB
    chipset: ws2812
    default_transition_length: 0s
    effects:
      - pulse:
          name: "Pulse"
          transition_length: 300ms
          update_interval: 300ms
          min_brightness: 50%
          max_brightness: 100%

      - addressable_twinkle:
          name: "Working"
          twinkle_probability: 5%
          progress_interval: 3ms
      - addressable_color_wipe:
          name: "Wakeword"
          colors:
            - red: 0%
              green: 50%
              blue: 0%
              num_leds: 12
          add_led_interval: 40ms
          reverse: false
      - pulse:
          name: "Slow Pulse"
          transition_length: 0.5s
          update_interval: 1s
          min_brightness: 0%
          max_brightness: 100%
      - pulse:
          name: "Fast Pulse"
          transition_length: 50ms
          update_interval: 100ms
          min_brightness: 50%
          max_brightness: 100%

switch:
  - platform: template
    name: Mute
    id: mute
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    entity_category: config
    on_turn_off:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
            - if:
                condition:
                  not:
                    - voice_assistant.is_running
                then:
                  - if:
                      condition:
                        lambda: return id(wake_word_engine_location).state == "In Home Assistant";
                      then:
                        - lambda: id(voice_asst).set_use_wake_word(true);
                        - voice_assistant.start_continuous
                  - if:
                      condition:
                        lambda: return id(wake_word_engine_location).state == "On device";
                      then:
                        - micro_wake_word.start
            - script.execute: reset_led
    on_turn_on:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - lambda: id(voice_asst).set_use_wake_word(false);
            - voice_assistant.stop
            - micro_wake_word.stop
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
            - script.execute: reset_led
  - platform: restart
    name: "${name} Restart"
select:
  - platform: template
    entity_category: config
    name: Wake word engine location
    id: wake_word_engine_location
    optimistic: true
    restore_value: true
    options:
      - In Home Assistant
      - On device
    initial_option: On device
    on_value:
      - 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(voice_asst).set_use_wake_word(true);
                  - voice_assistant.start_continuous:
      - if:
          condition:
            lambda: return x == "On device";
          then:
            - lambda: id(voice_asst).set_use_wake_word(false);
            - voice_assistant.stop
            - delay: 500ms
            - 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}

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
    publish_initial_state : True
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
    publish_initial_state : True
  - platform: template
    name: "${friendly_name} Set"
    id: btn_set
    publish_initial_state : True
  - platform: template
    name: "${friendly_name} Play"
    id: btn_play
    publish_initial_state : True
  - platform: template
    name: "${friendly_name} Mode"
    id: btn_mode
    publish_initial_state : True
  - platform: template
    name: "${friendly_name} Record"
    id: btn_record
    publish_initial_state : True
    on_press:
      - voice_assistant.start:
      - light.turn_on:
          id: led_ring
          blue: 0%
          red: 0%
          green: 100%
          brightness: 100%
          effect: "Wakeword"
#    on_release:
#      - voice_assistant.stop:
#      - output.turn_off: pa_ctrl
#      - light.turn_off:
#          id: led_ring
sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 39 #8
    attenuation: 11db
    update_interval: 15ms
    filters:
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 1
      - delta: 0.1
    on_value_range:
      - below: 0.55
        then:
          - binary_sensor.template.publish:
              id: btn_volume_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_volume_down
              state: ON
      - above: 1.02
        below: 1.33
        then:
          - binary_sensor.template.publish:
              id: btn_set
              state: ON
      - above: 1.43
        below: 1.77
        then:
          - binary_sensor.template.publish:
              id: btn_play
              state: ON
      - above: 1.87
        below: 2.15
        then:
          - binary_sensor.template.publish:
              id: btn_mode
              state: ON
      - above: 2.25
        below: 2.56
        then:
          - binary_sensor.template.publish:
              id: btn_record
              state: ON
      - above: 2.8
        then:
          - binary_sensor.template.publish:
              id: btn_volume_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_volume_down
              state: OFF
          - binary_sensor.template.publish:
              id: btn_set
              state: OFF
          - binary_sensor.template.publish:
              id: btn_play
              state: OFF
          - binary_sensor.template.publish:
              id: btn_mode
              state: OFF
          - binary_sensor.template.publish:
              id: btn_record
              state: OFF

Trying to build this on esphome in a docker container and i keep getting a build error. Has anyone seen this or know of a solution?

INFO ESPHome 2024.3.0
INFO Reading configuration /config/alexa-masterbedroom.yaml...
INFO Generating C++ source...
Traceback (most recent call last):
  File "/usr/local/bin/esphome", line 33, in <module>
    sys.exit(load_entry_point('esphome', 'console_scripts', 'esphome')())
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 1061, in main
    return run_esphome(sys.argv)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 1048, in run_esphome
    rc = POST_CONFIG_ACTIONS[args.command](args, config)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 478, in command_run
    exit_code = write_cpp(config)
                ^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 193, in write_cpp
    return write_cpp_file()
           ^^^^^^^^^^^^^^^^
  File "/esphome/esphome/__main__.py", line 211, in write_cpp_file
    writer.write_cpp(code_s)
  File "/esphome/esphome/writer.py", line 344, in write_cpp
    copy_src_tree()
  File "/esphome/esphome/writer.py", line 297, in copy_src_tree
    copy_files()
  File "/esphome/esphome/components/esp32/__init__.py", line 676, in copy_files
    repo_dir, _ = git.clone_or_update(
                  ^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/git.py", line 95, in clone_or_update
    old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/esphome/esphome/git.py", line 32, in run_git_command
    raise cv.Invalid(err_str)
voluptuous.error.Invalid: fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).

Cleaned my build files a few times and bam, its installing. Wouldnt work after just a couple cleans but eventually got it installing without error.

@ginandbacon curious where you found the yaml for the korvo v1.1 above? You mentioned they’ve almost got it working, but im not sure who or where they are? LOL. BTW, i did test that yaml out on a v1.1 and i couldnt get it to detect my wake word (alexa).

1 Like

You better believe it, micro wake word works on the korvo v1.1 and probably on any esp32 with psram.

@michel72 excited to see how it goes. Is that yaml above the one you’re using on a v1.1 korvo or do you mind sharing your configs?

It’s the one above! Far field satellite with an Elegant 3d printed enclosures - #46 by ginandbacon

Enable psram!

Look at that! Its working. Thanks @michel72 . Curious, does microwakeword perform better than via HA? I hope so as that is what i am struggling with at the moment.

Im having an issue with the playback on a HA speaker. Seems like the lambda isnt working as expected with microwakeword or something.

Logs:

Logger: pysqueezebox.player
Source: components/squeezebox/media_player.py:495
First occurred: 1:46:41 PM (12 occurrences)
Last logged: 4:25:07 PM

Timed out waiting for playlist_urls to have value [{'url': " Turn on the living room. I couldn't understand that."}]
Timed out waiting for playlist_urls to have value [{'url': ' Turn off the living room lights'}]
Timed out waiting for playlist_urls to have value [{'url': ' Turn off the living room lights.'}]
Timed out waiting for playlist_urls to have value [{'url': ' Turn on the living room lights.'}]
Timed out waiting for playlist_urls to have value [{'url': ' Turn out a little bit in white.'}]
  on_stt_end:
    - homeassistant.service:
        service: media_player.play_media
        data:
          entity_id: media_player.the_kitchen
          media_content_id: !lambda 'return x;'
          media_content_type: music
          announce: "true"

Also, i’ve noticed that at times i get stuck in the “Assist in progress” stage and the device doesnt seem to toggle out of that until i switch the Wake Word engine location to HA and back again followed by a muting/unmuting. There must be some state it gets in where its waiting for something to change but it doesnt until you force it to.

Sounds like that’s cause by the on_error part but I could be wrong. I haven’t tried playing music through the Korvo-1 or on another media_player via voice using something like SpotCast so I can’t really speculate on the issue. I got the V1.1 ESPHome code from the below. I do remember reading others mentioning having the same issue, possibly in the below link though. Scroll all the way to the bottom, it’s maybe 4 posts above the last post. Probably best to check there as the person who originally posted it said he’s still working on some minor issues.

Also, apparently I own both the V1.1 and -1 (not really). Both versions use the same microphone/led board. On the back of that it says Korvo V1.1. Only the bottom board is different between the 2 and on the bottom it says Korvo-1 V5. Makes it a bit confusing.

I was noticing some random “lock ups” for lack of a better word and did some tweaking of the korvo-1 code and haven’t had an issue yet but I did that late last night so not enough time to say it “solved” the problem. If it fixes it, I could easily update the V1.1 code because I think the part that fixes it is in both. I just want to give it a bit more time before posting it to be sure.

1 Like

Keep me posted on your updates. Much appreciated.

Sorry for the delay, ESPHome started having issues and I had to do a restore (auto backup or whatever add on/integration is a must). Everything works, pretty sure there is some code in there that is not needed. LED’s aren’t quite how I like but I haven’t had time to work on that. It works and doesn’t lock up or freeze, I added a restart switch to have alongside the factory reset switch in case you just want to reboot also. Remeber to replace the api= with your api key (or blank if you don’t use one). Probably going to have to clean the build files but worth a shot of just installing without doing that but if there are any errors clean the build files and try again and it should work, let me know if there are issues.

substitutions:
  name: "korvo"
  friendly_name: korvo

  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"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  name_add_mac_suffix: true
  platformio_options:
    board_build.flash_mode: dio
    upload_speed: 460800
  project:
    name: esphome.voice-assistant
    version: "1.0"
  min_version: 2023.11.5
  on_boot:
    - priority: 600
      then:
        - light.turn_on:
            id: led_ring
            red: 0%
            blue: 0%
            green: 100%
            brightness: 100%
            effect: random
        - if:
            condition:
              lambda: return id(init_in_progress);
            then:
              - lambda: id(init_in_progress) = false;

esp32:
  board: esp32s3box
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_AUDIO_BOARD_CUSTOM: "y"
      CONFIG_ESP32_S3_KORVO1_BOARD: "y"
    components:
      - name: esp32_s3_korvo1_board
        #source: github://espressif/components/hardware_driver@main
        source: github://abmantis/esphome_custom_audio_boards@main
        refresh: 0s

psram:
  mode: octal
  speed: 80MHz

external_components:
  - source: github://pr#5230
    components: esp_adf

ota:
logger:
api:
  encryption:
     key: api=
  on_client_connected:
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - delay: 20ms
            - ble.disable:
      - light.turn_on:
          id: led_ring
          blue: 0%
          red: 0%
          green: 100%
          brightness: 50%
          effect: connecting              
  on_client_disconnected:
    then:
      - ble.enable
      - light.turn_on:
          id: led_ring
          blue: 0%
          red: 100%
          green: 100%
          brightness: 50%
          effect: connecting

dashboard_import:
  package_import_url: github://esphome/firmware/voice-assistant/esp32-s3-korvo1.yaml@main

wifi:
  ssid: !secret wifi_ssid 
  password: !secret wifi_password
  use_address: 192.168.0.48
  ap:
  on_connect:
    then:
      - delay: 20ms # Gives time for improv results to be transmitted
      - ble.disable:
      - delay: 5s
  on_disconnect:
    then:
      - ble.enable:

improv_serial:

esp32_improv:
  authorizer: none

button:
  - platform: factory_reset
    id: factory_reset_btn
    name: Factory reset

esp_adf:
  board: esp32s3korvo1

microphone:
  - platform: esp_adf
    id: korvo_mic

speaker:
  - platform: esp_adf
    id: korvo_speaker

micro_wake_word:
  model: hey_jarvis 
# model: okay_nabu
  on_wake_word_detected:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;
    - light.turn_on:
        id: led_ring      
        red: 30%
        green: 30%
        blue: 70%
        brightness: 100%
        effect: wakeword
  

voice_assistant:
  id: voice_asst
  microphone: korvo_mic
  speaker: korvo_speaker
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 3.0
  vad_threshold: 3
  on_listening:     
    - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};    
    - light.turn_on:
        id: led_ring
        blue: 100%
        red: 0%
        green: 0%
        brightness: 100%
        effect: pulse
  #  - script.execute: reset_led    
  on_stt_vad_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
    - light.turn_on:
        id: led_ring
        blue: 0%
        red: 50%
        green: 50%
        brightness: 100%        
        effect: working
  #  - script.execute: reset_led    
  on_tts_stream_start:
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - light.turn_on:
        id: led_ring
        blue: 50%
        red: 50%
        green: 0%
        brightness: 100%        
        effect: pulse
    #- script.execute: reset_led    
  on_tts_stream_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
    - delay: 300ms
    - light.turn_on:
        id: led_ring
        blue: 0%
        red: 0%
        green: 100%
        brightness: 20%        
        effect: connecting
   # - script.execute: reset_led    
  on_error:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:
          - lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};
          - delay: 1s
          - if:
              condition:
                switch.is_off: mute
              then:
                - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
                - script.execute: reset_led
              else:
                - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
  on_client_connected:
    - if:
        condition:
          switch.is_off: mute
        then:
          - wait_until:
              not: ble.enabled
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
          - script.execute: reset_led
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false;
  on_client_disconnected:
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};;
 #   - lambda: |-
#        if (code == "wake-provider-missing" || code == "wake-#engine-missing") {
 #         id(use_wake_word).turn_off();
 #       }

script:
  - id: reset_led
    then:
      - if:
          condition:
            switch.is_on: use_wake_word
          then:
            - light.turn_on:
                id: led_ring
                blue: 100%
                red: 100%
                green: 0%
                brightness: 100%
                effect: connecting
          else:
            - light.turn_off: led_ring

switch:
  - platform: gpio
    id: pa_ctrl
    pin: GPIO38
    name: "${friendly_name} Speaker Mute"
    restore_mode: ALWAYS_ON

  - platform: template
    name: Mute
    id: mute
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    entity_category: config
    on_turn_off:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - lambda: id(voice_asst).set_use_wake_word(true);
            - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
            - if:
                condition:
                  not:
                    - voice_assistant.is_running
                then:
                  - voice_assistant.start_continuous
    on_turn_on:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - voice_assistant.stop
            - lambda: id(voice_asst).set_use_wake_word(false);
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};

  - platform: restart
    name: "korvo restart"

  - platform: template
    name: Use wake word
    id: use_wake_word
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    entity_category: config
    on_turn_on:
      - lambda: id(voice_asst).set_use_wake_word(true);
      - if:
          condition:
            not:
              - voice_assistant.is_running
          then:
            - voice_assistant.start_continuous
      #- script.execute: reset_led
    on_turn_off:
      - voice_assistant.stop
      - script.execute: reset_led

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}

light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    is_rgbw: true
    rgb_order: GRB    
    pin: GPIO19
    num_leds: 12
    rmt_channel: 0
    chipset: WS2812
    name: "${friendly_name} Light"
    default_transition_length: 1s
    effects:
      - addressable_scan:
          name: "led_12"
          move_interval: 10ms
          scan_width: 12
      - pulse:
          name: "pulse"
          transition_length: 0.5s
          update_interval: 0.5s
      - addressable_twinkle:
          name: "working"
          twinkle_probability: 5%
          progress_interval: 4ms
      - addressable_color_wipe:
          name: "wakeword"
          colors:
            - red: 0%
              green: 0%
              blue: 100%
              num_leds: 12
          add_led_interval: 20ms
          reverse: false
      - addressable_color_wipe:
          name: "connecting"
          colors:
            - red: 40%
              green: 30%
              blue: 30%
              num_leds: 12
          add_led_interval: 50ms
          reverse: true

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
  - platform: template
    name: "${friendly_name} Set"
    id: btn_set
    on_multi_click:
      - timing:
          - ON for at least 10s
        then:
          - button.press: factory_reset_btn    
  - platform: template
    name: "${friendly_name} Play"
    id: btn_play
  - platform: template
    name: "${friendly_name} Mode"
    id: btn_mode
    on_press:
      - voice_assistant.start_continuous:
  - platform: template
    name: "${friendly_name} Record" 
    id: btn_record
    on_press:
      #- voice_assistant.start_continuous:
      - lambda: id(voice_asst).set_use_wake_word(true);
          
sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 8
    attenuation: 11db
    update_interval: 15ms
    filters:
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 1
      - delta: 0.1
    on_value_range:
      - below: 0.55
        then:
          - binary_sensor.template.publish:
              id: btn_volume_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_volume_down
              state: ON
      - above: 1.02
        below: 1.33
        then:
          - binary_sensor.template.publish:
              id: btn_set
              state: ON
      - above: 1.43
        below: 1.77
        then:
          - binary_sensor.template.publish:
              id: btn_play
              state: ON
      - above: 1.87
        below: 2.15
        then:
          - binary_sensor.template.publish:
              id: btn_mode
              state: ON
      - above: 1.01
        below: 2.56
        then:
          - binary_sensor.template.publish:
              id: btn_record
              state: ON
      - above: 2.3
        then:
          - binary_sensor.template.publish:
              id: btn_volume_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_volume_down
              state: OFF
          - binary_sensor.template.publish:
              id: btn_set
              state: OFF
          - binary_sensor.template.publish:
              id: btn_play
              state: OFF
          - binary_sensor.template.publish:
              id: btn_mode
              state: OFF
          - binary_sensor.template.publish:
              id: btn_record
              state: OFF

Oh yeah, might as well post some pics of the 3D case. I’m probably going to scale it up so a 3.5mm right angle cable. That way I can route both cables through the same hole. I had to use a Dremel tool to get to the 3.5mm jack that’s straight. Kind of wished they didn’t place the 3.5mm output right next to the ribbon cable. It hasn’t caused any issues but a poor design choice IMO.

Very cool man. I have the same print but in all black. Gotta redo it cuz of some adherence issues with my printer but oh well. Excited to test out the new yaml. Much appreciated!