Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voice Assistant-Add support for the espressif esp32-korvo-v1.1 #2430

Open
rarroyo6 opened this issue Oct 15, 2023 · 139 comments
Open

Voice Assistant-Add support for the espressif esp32-korvo-v1.1 #2430

rarroyo6 opened this issue Oct 15, 2023 · 139 comments

Comments

@rarroyo6
Copy link

Describe the problem you have/What new integration you would like

Add support for the espressif esp32-korvo-v1.1, see documentation here: https://github.com/espressif/esp-skainet/blob/master/docs/en/hw-reference/esp32/user-guide-esp32-korvo-v1.1.md
Please describe your use case for this integration and alternatives you've tried:

This will provide another option for a voice assistant with all the needed features built-in.
Additional context

This board is available, relatively inexpensive, and has a microphone array, leds, and speaker output.

@trip5
Copy link

trip5 commented Oct 17, 2023

Searching, got me intrigued... The Korvo appears to be It looks like it may be using the same codebase as the what Espressif calls ESP-BOX (ie the ESP32-S3-BOX)... which puts it under the scope of #2239 - doesn't it?

@huishizhao
Copy link

hope someone could study how to add it into ESPHOME. it's it include a ADC es7210 and es8311CODEC and LED control chip.

@rarroyo6
Copy link
Author

Got one on order, will start playing with it as soon as I receive it.

@joey-90
Copy link

joey-90 commented Oct 20, 2023

I managed to get an ESP32-s3-Korvo-1 wooing with the setup posted https://github.com/joey-90/ESP32-S3-Korvo-1---Voice-Assistant/blob/main/voiceassistant.yaml

I can't get wake word detection working at the moment though

@asve99
Copy link

asve99 commented Oct 24, 2023

I managed to get an ESP32-s3-Korvo-1 wooing with the setup posted https://github.com/joey-90/ESP32-S3-Korvo-1---Voice-Assistant/blob/main/voiceassistant.yaml

I can't get wake word detection working at the moment though

Thanks @joey-90 for this. I copied the contents of the yaml from your repo and tried to compile/load for my board but I get these msgs when compiling. The firmware loads into the board but none of the i2s components load/work. Have you seen these warnings/errors when compiling? I am running ESPHome 2023.10.3.

src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp: In member function 'void esphome::i2s_audio::I2SAudioMicrophone::start_()':
src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp:62:3: warning: missing initializer for member 'i2s_driver_config_t::chan_mask' [-Wmissing-field-initializers]
   };
   ^
src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp:62:3: warning: missing initializer for member 'i2s_driver_config_t::total_chan' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp:62:3: warning: missing initializer for member 'i2s_driver_config_t::left_align' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp:62:3: warning: missing initializer for member 'i2s_driver_config_t::big_edin' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp:62:3: warning: missing initializer for member 'i2s_driver_config_t::bit_order_msb' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp:62:3: warning: missing initializer for member 'i2s_driver_config_t::skip_msk' [-Wmissing-field-initializers]
Compiling .pioenvs/esp32-voice-3/src/esphome/components/light/automation.o
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp: In static member function 'static void esphome::i2s_audio::I2SAudioSpeaker::player_task(void*)':
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp:56:3: warning: missing initializer for member 'i2s_driver_config_t::chan_mask' [-Wmissing-field-initializers]
   };
   ^
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp:56:3: warning: missing initializer for member 'i2s_driver_config_t::total_chan' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp:56:3: warning: missing initializer for member 'i2s_driver_config_t::left_align' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp:56:3: warning: missing initializer for member 'i2s_driver_config_t::big_edin' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp:56:3: warning: missing initializer for member 'i2s_driver_config_t::bit_order_msb' [-Wmissing-field-initializers]
src/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp:56:3: warning: missing initializer for member 'i2s_driver_config_t::skip_msk' [-Wmissing-field-initializers]

@netweaver1970
Copy link

I have the same compilation warnings and the same (non VAD working) behaviour with my Korvo-2. I used the S3-box yaml. Same warnings during compilation, looking good during the device esphome startup log (as far as I know) but also no wakeword detection. As per Jessie it should work very similar to the S3-BOX though ... https://discord.com/channels/429907082951524364/1163574334472863815/1166079003950588006

@joey-90
Copy link

joey-90 commented Oct 24, 2023

I've updated the code posted in the repository as I have now got wake word detection working.

In team of compile errors, I did get some initially but was mostly caused by fat fingering repository names and not having the correct pin layout. I think the Korvo 2 and the non S3 Korvo have different Pin outs.

The key to getting it working mostly for me was using the esp32-s3-devkitc-1 board variant.

Have you got the code for this posted anywhere?

@asve99
Copy link

asve99 commented Oct 24, 2023

I've updated the code posted in the repository as I have now got wake word detection working.

In team of compile errors, I did get some initially but was mostly caused by fat fingering repository names and not having the correct pin layout. I think the Korvo 2 and the non S3 Korvo have different Pin outs.

The key to getting it working mostly for me was using the esp32-s3-devkitc-1 board variant.

Have you got the code for this posted anywhere?

Thanks, I'll dbl chk my board version, gpio pins.

@asve99
Copy link

asve99 commented Oct 25, 2023

I managed to get this working...
As it turns out my esp32-korvo-v1.1 is not an esp32-s3, so I needed to change the board and the gpio pinouts.
My board is this one: https://dl.espressif.com/dl/schematics/ESP32-KORVO_V1.1_schematics.pdf
I am not 100% which board this should be in the esphome yaml, but I used the following:

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    version: recommended
    sdkconfig_options:
      CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y"

Heres' the gpio pinouts I used:

i2c:
  sda: GPIO19 #GPIO1
  scl: GPIO32 #GPIO2
  scan: true
  frequency: 400kHz

output:
  - platform: gpio
    id: pa_ctrl
    pin: GPIO12 #GPIO38

i2s_audio:
  - id: codec
    i2s_lrclk_pin: GPIO22 #GPIO41 #ws
    i2s_bclk_pin: GPIO25 #GPIO40 #clk
    i2s_mclk_pin: GPIO0 #GPIO42
  - id: mic_adc
    i2s_lrclk_pin: GPIO26 #GPIO9 #ws
    i2s_bclk_pin: GPIO27 #GPIO10 #clk
    i2s_mclk_pin: GPIO0 #GPIO20

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

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

light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO33 #GPIO19

sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 39 #8

The microphone, wakeword, LED all work fine.
I currently dont have an external speaker to test that.

A big thank you to @joey-90 for the yaml.

@trip5
Copy link

trip5 commented Oct 25, 2023

A-ha. Is it the WROOM-32-based module here? https://www.aliexpress.com/item/1005002803964499.html

For about $30, it's a steal if this can actually act as a voice assistant.

@asve99
Copy link

asve99 commented Oct 25, 2023

A-ha. Is it the WROOM-32-based module here? https://www.aliexpress.com/item/1005002803964499.html

For about $30, it's a steal if this can actually act as a voice assistant.

Yes, that's the one I am using.

@rarroyo6
Copy link
Author

That's the same board I ordered. Thanks to you guys for the work, I'll try it out as soon as I receive it.
I'm thinking of 3D printing a small cylindrical case for it, with the board and microphones on top, and the speaker on the bottom.
I'll post it after I get it designed.

@pyrodex
Copy link

pyrodex commented Oct 28, 2023

I managed to get this working...

As it turns out my esp32-korvo-v1.1 is not an esp32-s3, so I needed to change the board and the gpio pinouts.

My board is this one: https://dl.espressif.com/dl/schematics/ESP32-KORVO_V1.1_schematics.pdf

I am not 100% which board this should be in the esphome yaml, but I used the following:


esp32:

  board: esp32dev

  framework:

    type: esp-idf

    version: recommended

    sdkconfig_options:

      CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y"

Heres' the gpio pinouts I used:


i2c:

  sda: GPIO19 #GPIO1

  scl: GPIO32 #GPIO2

  scan: true

  frequency: 400kHz



output:

  - platform: gpio

    id: pa_ctrl

    pin: GPIO12 #GPIO38



i2s_audio:

  - id: codec

    i2s_lrclk_pin: GPIO22 #GPIO41 #ws

    i2s_bclk_pin: GPIO25 #GPIO40 #clk

    i2s_mclk_pin: GPIO0 #GPIO42

  - id: mic_adc

    i2s_lrclk_pin: GPIO26 #GPIO9 #ws

    i2s_bclk_pin: GPIO27 #GPIO10 #clk

    i2s_mclk_pin: GPIO0 #GPIO20



speaker:

  - platform: i2s_audio

    id: external_speaker

    dac_type: external

    i2s_audio_id: codec

    i2s_dout_pin: GPIO13 #GPIO39

    mode: mono



microphone:

  - platform: i2s_audio

    id: external_mic

    adc_type: external

    i2s_audio_id: mic_adc

    i2s_din_pin: GPIO36 #GPIO11

    pdm: false



light:

  - platform: esp32_rmt_led_strip

    id: led_ring

    name: "${friendly_name} Light"

    pin: GPIO33 #GPIO19



sensor:

  - id: button_adc

    platform: adc

    internal: true

    pin: 39 #8



The microphone, wakeword, LED all work fine.

I currently dont have an external speaker to test that.

A big thank you to @joey-90 for the yaml.

Can you post somewhere the full YAML? I just ordered two of these, one for my test bench and one to create a nice setup once I can get a cool 3D printed case.

@asve99
Copy link

asve99 commented Nov 2, 2023

The microphone, wakeword, LED all work fine.
I currently dont have an external speaker to test that.
A big thank you to @joey-90 for the yaml.

Can you post somewhere the full YAML? I just ordered two of these, one for my test bench and one to create a nice setup once I can get a cool 3D printed case.

This is the full yaml I used. Please note I didn't manage to successfully test the speaker output. I am not sure if it was the speaker I was using or the yaml cfg.

substitutions:
  friendly_name: esp32-voice-3

esphome:
  name: esp32-voice-3
  platformio_options:
    board_build.flash_mode: dio
  on_boot:
    - priority: -100
      then:
        - wait_until: api.connected
        - delay: 1s
        - if:
            condition:
              switch.is_on: use_wake_word
            then:
              - voice_assistant.start_continuous:

esp32:
  board: esp-wrover-kit
  framework:
    #type: esp-idf
    type: arduino
    version: recommended

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

ota:
  password: <REDACTED>

wifi:
  ssid: <REDACTED>
  password: <REDACTED>
  use_address: <REDACTED>

i2c:
  sda: GPIO19 #GPIO1
  scl: GPIO32 #GPIO2
  scan: true
  frequency: 400kHz

es8311:
  address: 0x18

es7210:
  address: 0x40

output:
  - platform: gpio
    id: pa_ctrl
    pin: GPIO12 #GPIO38

i2s_audio:
  - id: codec
    i2s_lrclk_pin: GPIO22 #GPIO41 #ws
    i2s_bclk_pin: GPIO25 #GPIO40 #clk
    i2s_mclk_pin: GPIO0 #GPIO42
  - id: mic_adc
    i2s_lrclk_pin: GPIO26 #GPIO9 #ws
    i2s_bclk_pin: GPIO27 #GPIO10 #clk
    i2s_mclk_pin: GPIO0 #GPIO20

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

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

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 2
  auto_gain: 15dBFS
  volume_multiplier: 0.5
  use_wake_word: false
  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: 100ms
    - 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: 30%
                red: 0%
                green: 0%
                brightness: 25%
                effect: none
          else:
            - light.turn_off: led_ring

switch:
  - 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: GPIO33 #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: 40ms
          reverse: false

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
  - platform: template
    name: "${friendly_name} Record"
    id: btn_record
    on_press:
      - output.turn_on: pa_ctrl
      - voice_assistant.start:
      - light.turn_on:
          id: led_ring
          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
    ```

@tobsch
Copy link

tobsch commented Nov 3, 2023

Great input folks! Just received my device and tested it out.
Problem with @asve99's example: I receive a lot of errors telling me that the speaker buffer is full:

[21:25:52][W][voice_assistant:283]: Receive buffer full.
[21:25:52][W][voice_assistant:293]: Speaker buffer full.

I have a classic wired apple headphone connected to the jack.
Any ideas?

@trip5
Copy link

trip5 commented Nov 3, 2023

Has anyone tried putting an enclosure on this yet? I'm also curious about the dimensions. The only spec I found said it's 88mm wide but no mention of the height - especially since it appears to be 2 boards strapped together... pretty hard to find 2 prefabricated plastic enclosures... Is 3D printing the best option?

@rarroyo6
Copy link
Author

rarroyo6 commented Nov 3, 2023

Just received my board today. If I get a chance this weekend to set it up, I'll work on designing a case that I have mind.
3D printing is the best option for this. I'll post my design when I finish it.

@rarroyo6
Copy link
Author

rarroyo6 commented Nov 5, 2023

Just tried to program the board. When I connect the USB, it does not get recognized as a serial port, when I connect a FTDI board to the pads on the PCB, it does not recognize the type of ESP chip.
Did you guys have to do anything special to program it, or could it be that I got a defective board?

@asve99
Copy link

asve99 commented Nov 5, 2023

Just tried to program the board. When I connect the USB, it does not get recognized as a serial port, when I connect a FTDI board to the pads on the PCB, it does not recognize the type of ESP chip. Did you guys have to do anything special to program it, or could it be that I got a defective board?

Initially I thought I had the same issue with my board (not detected), but the issue was that I needed to connect power to the board (via the usb power connector) and connect your PC via the UART usb port on the board.

@rarroyo6
Copy link
Author

rarroyo6 commented Nov 6, 2023

Never mind, thanks for the help.
Apparently the USB port on my computer had locked-up or something.
I restarted my computer and it recognized it.

@abmantis
Copy link

abmantis commented Nov 9, 2023

@joey-90 thanks for your config! It works great except the speaker. Did you got that to work? On mine there is no sound (I'm using the headphone jack)

@rarroyo6
Copy link
Author

I couldn't get the speaker to work either. I verified the I/O pins against the schematic, and they seem to match correctly.
The following is what I get from the logs:

[21:42:04][D][voice_assistant:502]: Event Type: 8
[21:42:04][D][voice_assistant:572]: Response URL: "http://192.168.1.31:8123/api/tts_proxy/093290dcda7b2989879ced0ee701d7978ed0c34a_en-us_bcc7c1a994_tts.piper.raw"
[21:42:04][D][voice_assistant:395]: State changed from AWAITING_RESPONSE to STREAMING_RESPONSE
[21:42:04][D][voice_assistant:401]: Desired state set to STREAMING_RESPONSE
[21:42:04][D][i2s_audio.speaker:161]: Starting I2S Audio Speaker
[21:42:04][D][voice_assistant:502]: Event Type: 2
[21:42:04][D][voice_assistant:584]: Assist Pipeline ended
[21:42:04][D][i2s_audio.speaker:164]: Started I2S Audio Speaker
[21:42:04][D][voice_assistant:502]: Event Type: 98
[21:42:06][W][voice_assistant:293]: Speaker buffer full.
[21:42:06][W][voice_assistant:293]: Speaker buffer full.
[21:42:06][W][voice_assistant:293]: Speaker buffer full.
...
[21:42:07][W][voice_assistant:293]: Speaker buffer full.
[21:42:07][W][voice_assistant:293]: Speaker buffer full.
[21:42:07][D][voice_assistant:502]: Event Type: 99
[21:42:07][D][voice_assistant:395]: State changed from STREAMING_RESPONSE to RESPONSE_FINISHED
[21:42:07][D][voice_assistant:401]: Desired state set to IDLE
[21:42:07][D][voice_assistant:395]: State changed from RESPONSE_FINISHED to IDLE

@joey-90
Copy link

joey-90 commented Nov 11, 2023

Apologies for the delay in getting back to everyone, its been a busy week or two with work etc.

I also can't get the speaker output to work, the pin out is definitely correct. The same pin config is used in the demo firmware from Espressif. I get similar error messages to what you are seeing. I've got some updated yaml config to upload where I've been playing with some of the values for the Voice assistant etc.

@rarroyo6
Copy link
Author

Great! thank you, I would like to get this working.
I'm also trying a couple of things on my end.

@pascalmtts
Copy link

I received mine today and can't get Assistant to work. When pressing the record button and releasing it nothing happens and I also don't receive any events in Home Asssitant. The Debug Page of my Voice Assistant says: There where no events in this run.

Besides of that I can see the event of the button press and the LED state change in Home Assistant from the device.

I used the same config as @asve99

Anyone with an idea why this is not working for me?

@pyrodex
Copy link

pyrodex commented Nov 13, 2023

I received mine today and can't get Assistant to work. When pressing the record button and releasing it nothing happens and I also don't receive any events in Home Asssitant. The Debug Page of my Voice Assistant says: There where no events in this run.

Besides of that I can see the event of the button press and the LED state change in Home Assistant from the device.

I used the same config as @asve99

Anyone with an idea why this is not working for me?

The buttons aren't tied to any functions at the moment, you have to leverage a wake word from my personal experience.

@abmantis
Copy link

@pascalmtts see @joey-90 example on how to use the buttons. In his example voice is also working fine, but no sound output yet.

I am currently trying to add the Korvo1 board to ESP-ADF, which in turn should make it easy to get it into ESPHome.

@pascalmtts
Copy link

pascalmtts commented Nov 13, 2023

The button record is tied to a function in the template above:

  - platform: template
    name: "${friendly_name} Record"
    id: btn_record
    on_press:
      - output.turn_on: pa_ctrl
      - voice_assistant.start:
      - light.turn_on:
          id: led_ring
          brightness: 100%
          effect: "Wakeword"
    on_release:
      - voice_assistant.stop:
      - output.turn_off: pa_ctrl
      - light.turn_off:
          id: led_ring

As I did not get the wake word to work I tried using the record button, as it should start and stop the voice assistant.

I am currently trying to add the Korvo1 board to ESP-ADF, which in turn should make it easy to get it into ESPHome.

That's great! I don't know if you also have the Korvo1.1 on the list because I think these are 2 different boards. The Korvo1.1 is much cheaper then ESP32-S3-Korvo-1

Edit:

Here is a log output from ESPHome when pressing, holding and then releasing the rec button:

[15:51:54][D][binary_sensor:036]: 'ESP32 Korvo Record': Sending state ON
[15:51:54][D][voice_assistant:395]: State changed from IDLE to START_PIPELINE
[15:51:54][D][voice_assistant:401]: Desired state set to START_MICROPHONE
[15:51:54][D][light:036]: 'ESP32 Korvo Light' Setting:
[15:51:54][D][light:047]:   State: ON
[15:51:54][D][light:051]:   Brightness: 100%
[15:51:54][D][light:109]:   Effect: 'Wakeword'
[15:51:54][W][component:214]: Component adc.sensor took a long time for an operation (0.05 s).
[15:51:54][W][component:215]: Components should block for at most 20-30ms.
[15:51:54][D][voice_assistant:124]: microphone not running
[15:51:54][D][voice_assistant:206]: Requesting start...
[15:51:54][D][voice_assistant:395]: State changed from START_PIPELINE to STARTING_PIPELINE
[15:51:54][D][voice_assistant:124]: microphone not running
[15:51:54][D][voice_assistant:124]: microphone not running
[15:51:54][D][voice_assistant:124]: microphone not running
[15:51:54][D][voice_assistant:416]: Client started, streaming microphone
[15:51:54][D][voice_assistant:395]: State changed from STARTING_PIPELINE to START_MICROPHONE
[15:51:54][D][voice_assistant:401]: Desired state set to STREAMING_MICROPHONE
[15:51:54][D][voice_assistant:159]: Starting Microphone
[15:51:54][D][voice_assistant:395]: State changed from START_MICROPHONE to STARTING_MICROPHONE
[15:51:54][D][voice_assistant:395]: State changed from STARTING_MICROPHONE to STREAMING_MICROPHONE
[15:51:57][D][sensor:094]: 'button_adc': Sending state 3.13900 V with 2 decimals of accuracy
[15:51:57][D][binary_sensor:036]: 'ESP32 Korvo Volume Up': Sending state OFF
[15:51:57][D][binary_sensor:036]: 'ESP32 Korvo Volume Down': Sending state OFF
[15:51:57][D][binary_sensor:036]: 'ESP32 Korvo Set': Sending state OFF
[15:51:57][D][binary_sensor:036]: 'ESP32 Korvo Play': Sending state OFF
[15:51:57][D][binary_sensor:036]: 'ESP32 Korvo Mode': Sending state OFF
[15:51:57][D][binary_sensor:036]: 'ESP32 Korvo Record': Sending state OFF
[15:51:57][D][voice_assistant:495]: Signaling stop...
[15:51:57][D][voice_assistant:395]: State changed from STREAMING_MICROPHONE to STOP_MICROPHONE
[15:51:57][D][voice_assistant:401]: Desired state set to IDLE
[15:51:57][D][light:036]: 'ESP32 Korvo Light' Setting:
[15:51:57][D][light:047]:   State: OFF
[15:51:57][D][light:109]:   Effect: 'None'
[15:51:57][W][component:214]: Component adc.sensor took a long time for an operation (0.09 s).
[15:51:57][W][component:215]: Components should block for at most 20-30ms.
[15:51:57][D][voice_assistant:395]: State changed from STOP_MICROPHONE to STOPPING_MICROPHONE
[15:51:57][D][voice_assistant:395]: State changed from STOPPING_MICROPHONE to IDLE

@pascalmtts
Copy link

Alright I got it working. Problem was in my Assist configuration - after selecting Piper as TTS it is working now. Audio is also not working in my case, but I think we will find a solution for this in the future

@joey-90
Copy link

joey-90 commented Nov 17, 2023

I've uploaded new YAML config having made some tweaks.

These are:

  • Night light switch - enables a dim setting at night etc.
  • Adjusted Voice assistant sensitivity - these are for a noisy room so YMMV
  • Added a start up lighting routine.

Still no success with the sound output, but there is something to try. Will post over the weekend when I've had a chance to play.

Things to add:

  • Detect WiFi connection lost and set indicator light
  • Detect Home Assistant API status and set indicator lights
  • Graft in changes from recent changes made by Jesserockz in Atom firmwares
  • Look at using the SD card slot for local files

Please comment if there any suggestions and feel free to add pull requests if you see any errors or can add better code.

@Hedda
Copy link

Hedda commented Aug 15, 2024

@rarroyo6
Copy link
Author

@chymaslik
Copy link

chymaslik commented Nov 29, 2024

The board is still goofy and I am still learning. So what I have works on the esp32-korvo-1.1 but I am not sure if what I am doing is the correct way. Thie config has working audio out both the speaker and 3.5mm and also working volume buttons on the device and in HA. There is a volume sensor but that is so-so. I tried to make a custom sensor but could not get it to work so I made a generic sensor then passed the data to another sensor. I had to do it this way because the only way I could get the volume into the sensor was with the get_sensor_by_key and the only way to get the key was to find the specific named sensor. Again probably not the correct way and its reporting is slow. Any way this config works but with the esp_adf I modified and again probably not the correct way. And hopefully I can paste my yaml so it is formatted correctly. And yes most of this yaml is borrowed. So issues I have seen, have to reset after install, lights act wonky (I think there is some clock issues), the popping is still in the headphone but not in the speaker, only because the PA is being turned off and on with audio so the pop I believe happens before the PA is turned up and after it is turned down. These issues are not seen in the esp32-s3-korvo-1.5 (other than the headphone pop on initial audio stream and stop).

---
substitutions:
  name: homeassist03
  friendly_name: HomeAssist Speaker 3
  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}"
  min_version: 2024.5.5
  platformio_options:
    board_build.flash_mode: dio
  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
  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_ESP32S3_DATA_CACHE_64KB: y
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: y
      CONFIG_PARTITION_TABLE_CUSTOM: y
      CONFIG_PARTITION_TABLE_CUSTOM_FILENAME: "default_16MB.csv" 
      CONFIG_PARTITION_TABLE_FILENAME: "default_16MB.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
      CONFIG_AUDIO_BOARD_CUSTOM: y
      
    components:
      - name: esp32_korvo1_board #esp32_s3_korvo1_board for the s3 variant and really you should be able to name this anything
        source: github://dwitgen/korvo_1@main #s3_korvo_1 for the s3 variant
        refresh: 0s

external_components:
  - source: github://dwitgen/esphome@main #pr#5230
    components: [esp_adf]
    refresh: 0s
 
# Enable logging
logger:

ota:
  password: !secret haspk03_ota_pwd

# Enable Home Assistant API
api:
  encryption:
    key: !secret haspk03_api_key
  services:
    - service: volume_up
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

    - service: volume_down
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP Address"

time:
  platform: homeassistant
  id: homeassistant_time

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: !secret haspk03_ip
 
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Homeassist-Smartspeaker-03"
    password: !secret haspk03_ap_pwd

captive_portal:

output:
  - platform: gpio
    id: pa_ctrl
    pin:
      number: GPIO12
      ignore_strapping_warning: true

esp_adf:
  board: esp32korvo1 #esp32s3korvo1 for s3 variant


speaker:
  - platform: esp_adf
    id: external_speaker
    
microphone:
  - platform: esp_adf
    id: external_mic

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 1
  auto_gain: 31dBFS
  volume_multiplier: 6.0
  vad_threshold: 1
  use_wake_word: false
  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: 0%
        green: 100%
        brightness: 50%
        effect: pulse
  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:
    - 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
        then:
          - wait_until:
              not:
                voice_assistant.is_running:
         
  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:
          - lambda: id(voice_asst).set_use_wake_word(true);
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false;
    - script.execute: reset_led

  on_client_disconnected:
    - lambda: id(voice_asst).set_use_wake_word(false);
    - voice_assistant.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: 100%
                      red: 0%
                      green: 0%
                      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

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:
                  - lambda: id(voice_asst).set_use_wake_word(true);
                  - voice_assistant.start_continuous
            - 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
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
            - script.execute: reset_led
  - platform: restart
    name: "${name} Restart"

light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO33
    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: 28%
              green: 100%
              blue: 90%
              num_leds: 12
          add_led_interval: 40ms
          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
      - addressable_color_wipe:
          name: "Thinking"
          colors:
            - red: 1%
              green: 90%
              blue: 99%
              num_leds: 2
            - red: 13%
              green: 17%
              blue: 87%
              num_leds: 6
          add_led_interval: 75ms
          reverse: true
      - 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%

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: speaker_volume
    type: int
    restore_value: yes
    initial_value: '50'  # Initial volume level (0-100)

button:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_vol_up
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_vol_down
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
  - 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"

# Status connection
  - platform: status
    name: "${friendly_name} Status"
sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 39
    attenuation: auto
    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_vol_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_vol_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_vol_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_vol_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

# Wifi signal
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
 
 # Generic volume sensor was used so it can be used in the esp_adf_speaker to get volume into HA
  - platform: template
    id: generic_volume_sensor 
    internal: true  

  - platform: template
    name: "${friendly_name} Volume"
    accuracy_decimals: 0
    id: scaled_volume_sensor
    lambda: |-
      // Scale the volume from 0-100 to 0-10
      return id(generic_volume_sensor).state / 10.0;

Hello @dwitgen
I tried this yaml and got an error that the repository "github://dwitgen/esphome@main" is not available. Are you no longer maintaining this repository?

@dwitgen
Copy link

dwitgen commented Nov 29, 2024

Unfortunately, I am not maintaining it.

@dotorg
Copy link

dotorg commented Dec 4, 2024

So you just tossed it, breaking anyone who had been using it? Did anyone else grab a fork of it?

@dwitgen
Copy link

dwitgen commented Dec 4, 2024

So you just tossed it, breaking anyone who had been using it? Did anyone else grab a fork of it?

The korvo 1 never did work correctly. If you let me know what repo/s you were using I can load them back up for a day or so and you can clone them. I was only doing some testing and put them up if anyone wanted to clone them. In hindsight I probably should not have done that as I really did not have the means to properly put something out for public use.

@dotorg
Copy link

dotorg commented Dec 4, 2024

Well, specifically, I think the problem people have with it is getting the speaker working. The matrix microphone is pretty straightforward. Your configuration is the only one I've seen, anywhere, where someone got sound output working. I don't know if its something in your esphome fork adding adf support, or something in your board definition. But other people's ones -- including for the 8311 -- seem to universally result in no sound output. So there's something you were doing in your branch to initialize the 8311, or how you configured the i2s to talk to it, that no one else has figured out. That could potentially be ported to someone else's components, but it'd take knowing what you'd done differently.

So I guess the esphome fork you had is probably the most useful one, but getting both where people can grab a copy would give the best odds of working out what you did differently.

Given how ideal a platform the Korvo-1 is, I think that's why people are so interested. Short of whatever the Nabu Casa people release, it's the best platform for a voice assistant.

@trip5
Copy link

trip5 commented Dec 4, 2024

My Korvo 1.1 broke my heart. Got it to work one night at least to listen to voice commands. Went to bed excited and tried to show my wife the next day and it made a fool out of me. Spent too many nights on that thing. It's a paperweight somewhere... for now.

That's a danger of this hobby. Too many hardware manufacturers release untested gear on the community and if we make it work, then it's a success... if not, the makers move onto something else.

But didn't the ESP32-S2 and S3 take 3-4 years to finally reach mainstream support in Tasmota and ESPHome? The Korvos are just getting started...

@dotorg
Copy link

dotorg commented Dec 4, 2024

But didn't the ESP32-S2 and S3 take 3-4 years to finally reach mainstream support in Tasmota and ESPHome? The Korvos are just getting started...

Well, the strange thing is there's not really any hardware in it that you don't find in other devices that work fine. So it's really some combination of the board definition and difference in the esp-adf that is doing it. I have other ESP32-S3 devices using the same or similar DAC and ADC chips, and they work fine. I suspect I'm going to end up standardizing on the Waveshare ESP32-S3-LCD-1.85, which works flawlessly for me, but doesn't have the microphone range. But I'd rather standardize on the Korvo-1 because of the better mic setup. In testing, my work flawlessly from anywhere in a fairly large room at "normal" talking levels, whereas I feel like I have to speak loudly to the Waveshare board.

But really, any of the Xtensa based ESP32s should, broadly speaking, work. The shift to Risc-V for the newer ESP32 chips, though, makes me concerned about ongoing support.

@djmillsuk
Copy link

djmillsuk commented Dec 6, 2024

I have the same compilation warnings and the same (non VAD working) behaviour with my Korvo-2. I used the S3-box yaml. Same warnings during compilation, looking good during the device esphome startup log (as far as I know) but also no wakeword detection. As per Jessie it should work very similar to the S3-BOX though ... https://discord.com/channels/429907082951524364/1163574334472863815/1166079003950588006

did you manage to get the Korvo-2 working? im having no luck at all, flashes and responds to wireless logs ok but speaker and mic dont work, i suspect the pin mapping is wrong

@MechlingBurgh
Copy link

I was using and having very good luck with it. I just had to reboot the first power on. I wish you would restore the depository for people. I just went to update it tonight and got the same error about the directory.

@dotorg
Copy link

dotorg commented Dec 8, 2024

@MechlingBurgh, does that mean you still have the last pull you did? I know a lot of people use ESPHome without a lot of technical experience, but do you know enough to change the git remote and push it up to another place on github, if so? A git remote set-url and a push is all it would take. Someone with a local repo clone can get it back online even if @dwitgen doesn't pop back up. Being able to see the patches made to esphome would be handy for anyone else figuring out why this branch worked and all the other ones people have done haven't been quite right. Even a copy of the code would be good, but a copy of the whole repo would keep the change history intact.

@dwitgen
Copy link

dwitgen commented Dec 8, 2024

So, the repos are back up, but I cannot maintain them if someone wants to pull them. I did not find the Korvo v1 to work very well. The main changes were to add support to the Korvo v1 board and add the board/pin configuration files (which is the test_boards repo) so that it would work with the ESP_ADF. There were some changes made to add volume and pa functions directly to the speaker component but again all this was just some testing on my part and cannot say this is the correct way for this to be done. Not sure how long they will be up, probably not past the first time they have an issue due to changes in ESPHome/ESP-IDF/ESP-ADF. Did not mean to cause any inconvenience.

@dotorg
Copy link

dotorg commented Dec 8, 2024

Thanks for that!

I've forked them, and will leave them up, for anyone who wants them. Hopefully it helps work out what the limitations were for other ADF ports, so we can get support working long-term.

https://github.com/dotorg/korvo_1_esphome
https://github.com/dotorg/korvo_1_boards

I have no plans to touch them, so any configs currently working with the old repos ought to keep working with these. If I end up making any incompatible changes, it'll be via a different component. But, honestly, I suspect the issue with the more common ADF ports not working is just an initialization one, and I believe it'll be possible to do that in straight YAML code, which will be more stable relative to whatever ADF port people are using.

@MechlingBurgh
Copy link

@MechlingBurgh, does that mean you still have the last pull you did? I know a lot of people use ESPHome without a lot of technical experience, but do you know enough to change the git remote and push it up to another place on github, if so? A git remote set-url and a push is all it would take. Someone with a local repo clone can get it back online even if @dwitgen doesn't pop back up. Being able to see the patches made to esphome would be handy for anyone else figuring out why this branch worked and all the other ones people have done haven't been quite right. Even a copy of the code would be good, but a copy of the whole repo would keep the change history intact.

Ya I will copy them to my local drive and change it in the yaml, thank you. I don't know a lot but I'm learning as I do more a lot of trail and error.

@chymaslik
Copy link

chymaslik commented Dec 12, 2024

@dwitgen thank you for your sources.
If anyone is interested, below is my configuration with a working speaker and wake word on device for ESP32-Korvo V1.1. I had to disable some features because I was getting a full buffer errors. And for some reason my device only works when both USBs are connected. Otherwise it works quite stably.

---
substitutions:
  name: esp32-korvo1-mww
  friendly_name: Korvo 1 microWakeWord
  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: 2024.5.5
  platformio_options:
    board_build.flash_mode: dio
  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_ESP32S3_DATA_CACHE_64KB: y
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: y
      CONFIG_PARTITION_TABLE_CUSTOM: y
      CONFIG_PARTITION_TABLE_CUSTOM_FILENAME: "default_16MB.csv"
      CONFIG_PARTITION_TABLE_FILENAME: "default_16MB.csv"
      CONFIG_PARTITION_TABLE_OFFSET: "0x8000"
      CONFIG_ESP32_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32_SPIRAM_SUPPORT: y
      CONFIG_SPIRAM_SPEED_80M: y
      CONFIG_SPIRAM_SIZE: "4194304" # 4MB
      CONFIG_SPIRAM_USE: y
      CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT: y
      CONFIG_I2S_ENABLE_DEBUG_LOG: y
      CONFIG_AUDIO_BOARD_CUSTOM: y

    components:
      - name: esp32_korvo1_board #esp32_s3_korvo1_board for the s3 variant and really you should be able to name this anything
        source: github://chymaslik/test_boards@main
        refresh: 0s

external_components:
  - source: github://chymaslik/esphome@main #pr#5230
    components: [esp_adf]
    refresh: 0s

# Enable logging
logger:
  logs:
    component: ERROR

ota:
  - platform: esphome
    password: "e4125183bb6d45bf43ab81cfd6160d23"

# Enable Home Assistant API
api:
  encryption:
    key: "mAP0fLzUWLw7MJfpOY3MquhMm6lEvbT9uzMPDWVyfgE="
  services:
    - service: volume_up
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

    - service: volume_down
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
# text_sensor:
#   - platform: wifi_info
#     ip_address:
#       name: "${friendly_name} IP Address"

# time:
#   platform: homeassistant
#   id: homeassistant_time

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.44.243
    subnet: 255.255.255.0
    gateway: 192.168.44.1

  # # Enable fallback hotspot (captive portal) in case wifi connection fails
  # ap:
  #   ssid: "Esp32-Korvo-1"
  #   password: !secret wifi_password

# captive_portal:

output:
  - platform: gpio
    id: pa_ctrl
    pin:
      number: GPIO12
      ignore_strapping_warning: true

esp_adf:
  board: esp32korvo1 #esp32s3korvo1 for s3 variant

speaker:
  - platform: esp_adf
    id: external_speaker

microphone:
  - platform: esp_adf
    id: external_mic

micro_wake_word:
  models: ${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: 31dBFS
  volume_multiplier: 2.0

  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:
    - delay: 100ms
    - 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: 100%
                      red: 0%
                      green: 0%
                      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: "Led strip light"
    pin: GPIO33
    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: 10%
          progress_interval: 5ms
      - addressable_color_wipe:
          name: "Wakeword"
          colors:
            - red: 28%
              green: 100%
              blue: 90%
              num_leds: 12
          add_led_interval: 40ms
          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
      - addressable_color_wipe:
          name: "Thinking"
          colors:
            - red: 1%
              green: 90%
              blue: 99%
              num_leds: 2
            - red: 13%
              green: 17%
              blue: 87%
              num_leds: 6
          add_led_interval: 75ms
          reverse: true
      - 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}
  - id: speaker_volume
    type: int
    restore_value: yes
    initial_value: "70" # Initial volume level (0-100)

button:
  - platform: template
    name: "Volume Up"
    id: btn_volume_up
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

  - platform: template
    name: "Volume Down"
    id: btn_volume_down
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }

binary_sensor:
  - platform: template
    name: "Volume Up"
    id: btn_vol_up
    publish_initial_state: True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }
  - platform: template
    name: "Volume Down"
    id: btn_vol_down
    publish_initial_state: True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
  - platform: template
    name: "Set"
    id: btn_set
    publish_initial_state: True
  - platform: template
    name: "Play"
    id: btn_play
    publish_initial_state: True
  - platform: template
    name: "Mode"
    id: btn_mode
    publish_initial_state: True
  - platform: template
    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"

  # Status connection
  - platform: status
    name: "Status"
sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 39
    attenuation: 12db
    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_vol_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_vol_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_vol_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_vol_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

  # # Wifi signal
  # - platform: wifi_signal
  #   name: "WiFi Signal"
  #   update_interval: 60s

  # Generic volume sensor was used so it can be used in the esp_adf_speaker to get volume into HA
  - platform: template
    id: generic_volume_sensor
    internal: true

  - platform: template
    name: "Current Volume"
    accuracy_decimals: 0
    id: scaled_volume_sensor
    lambda: |-
      // Scale the volume from 0-100 to 0-10
      return id(generic_volume_sensor).state / 10.0;

@dotorg
Copy link

dotorg commented Dec 12, 2024

That's super interesting -- there must be fairly substantial hardware revisions between the early boards like yours and the newer ones. Like, the PA GPIO has changed, which suggests the board itself was a new layout, not just component swaps or tweaks in later revisions.

I wonder if that's part of why some people are getting it to work and some aren't. @dwitgen's libraries didn't help on my V5 board -- they don't even compile, as they're clearly targeted at the earlier revisions. Reviewing the code, there's nothing unusual being done to initialize things, so I am starting to think fundamentally there's an ADF compatibility issue with the V4/V5 boards, and it has nothing to do with ESPHome or any of these external components.

@schneipk
Copy link

@dotorg I've got a Korvo V5.0 Board as well and although I am able to have it show up in my home assistant (LEDs and Button presses working), with a @abmantis code, there is no Audio playback and no received audio recordings. I sure hope you are more successful than me! Invested here and hoping for insights ;)

@dotorg
Copy link

dotorg commented Dec 13, 2024

My V5 works fine with everything but sound output. It's a little weird because i2s is pretty standardized, so it's a bit of a head scratcher why it isn't just working. There aren't any easily accessible test points to get a scope on to see if the problem is the DAC or the amp stages. I just know there's no output from the speaker or headphone outputs, as tested on an oscilloscope. The one thing I haven't tested yet is doing an Arduino framework test. The ES8311 is supported in Arduino, so that test not working may suggest it's an issue getting the amp stage powered up, vs an ESP-IDF/ADF problem. It's been on my list to try, just haven't had time yet. Unfortunately the ADC chip used by the mic isn't supported there, so it isn't a "solution", even if it started making the speaker work.

It doesn't help that Espressif's docs are terrible, and a lot of the sample code doesn't actually work. It's clear that whole line of boards is largely abandoned, or at least was never really invested in to produce quality support materials. I suspect they thought there was a market for 3rd party voice assistant hardware, and the massive losses at Amazon and Google in those markets gave them pause. The same thing seems to have happened with their Matter/Thread bridge hardware stack, too.

@dotorg
Copy link

dotorg commented Dec 13, 2024

So, even with a basic Arduino sketch, I can't get output from the ES8311 on the V5 board -- simple sketches that work fine on other boards with the same electronics. I also found as soon as I enable support for it via i2c, the microphone also stops working. The schematic is a mess -- it has the i2c address wrong, or on the wrong sheet, for example. There's some feedback from the DAC back to the ADC, which I think is made so if you're playing music on the unit, the ADC can filter it out and still detect speech.

I actually think either my board is defective or the design is -- I didn't realize the default firmware is supposed to respond with tones until I went digging in the source for it, and I hadn't remembered hearing them. I reflashed the factory firmware and, as expected, voice recognition worked but I got no audio output.

So, I may have been spinning my wheels if it turns out the board itself just isn't working.

@dwitgen
Copy link

dwitgen commented Dec 13, 2024

@dotorg The V5 you are referencing is a different ESP32 chip than the ESP32-korvo-v1.1. Here is the link to the esp32-korvo-v1.1: https://github.com/espressif/esp-skainet/blob/master/docs/en/hw-reference/esp32/user-guide-esp32-korvo-v1.1.md and here is the link to the esp32-s3-korvo-1: https://github.com/espressif/esp-skainet/blob/master/docs/en/hw-reference/esp32s3/user-guide-korvo-1.md. So, there are differences in the board pins the microphone boards are the same though. I am not in a position to maintain repos and I am not really knowledgeable enough either. I am going to put the 2 board configs I have and the modified esp-adf components that appear to work. I would suggest pulling them in the event something changes, and I have to drop them, again I have no plans to unless they break. Here is a working yaml with links to the repos for the ESP32-S3_Korvo-1 that should work with the V5 board this same config can be used with the ESP32-Korvo-v1.1 board by changing the components: ref: and the esp_adf: board:, there are comments at both these lines to show what needs changed. one thing I forgot was there is also a difference in the ADC and LED pins between the 2 boards. I have updated this with comments on the lines for ADC and LED pins.

substitutions:
  name: homeassist01
  friendly_name: HomeAssist Speaker 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"
 
esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"
  min_version: 2023.12.8
  platformio_options:
    board_build.flash_mode: dio
  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: esp32-s3-devkitc-1
  flash_size: 8MB
  framework:
    type: esp-idf
    version: recommended #4.4.6
    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"
            
    components:
      - name: esp32_s3_korvo_1_board
        source:
          type: git
          url: https://github.com/dwitgen/Korvo_AudioBoards
          ref: s3_korvo-1  # Use the "s3_Korvo-1" branch for ESP32-S3 model and use the "korvo-1" branch for the ESP32 model
        refresh: 0s
psram:
  mode: octal
  speed: 80MHz

external_components:
  - source:
      type: git
      url: https://github.com/dwitgen/esphome.git
      ref: main
    components: [ esp_adf ]
    refresh: 1d

    
    
# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret haspk01_api_key

ota:
  - platform: esphome
    password: !secret haspk01_ota_pwd

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

time:
  platform: homeassistant
  id: homeassistant_time

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: !secret haspk01_ip
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Homeassist-Smartspeaker-01"
    password: !secret haspk01_ap_pwd

captive_portal:

esp_adf:
  board: esp32s3korvo1 # required to ID board configs/settings, if korvo v1.1 use esp32korvo1 instead

speaker:
  - platform: esp_adf
    id: external_speaker
    
microphone:
  - platform: esp_adf
    id: external_mic

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 1
  auto_gain: 31dBFS
  volume_multiplier: 6.0
  vad_threshold: 1
  use_wake_word: false
  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: 0%
        green: 100%
        brightness: 50%
        effect: pulse
  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:
    - 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
        then:
          - wait_until:
              not:
                voice_assistant.is_running:
          
  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:
          - lambda: id(voice_asst).set_use_wake_word(true);
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false;
    - script.execute: reset_led

  on_client_disconnected:
    - lambda: id(voice_asst).set_use_wake_word(false);
    - voice_assistant.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: 100%
                      red: 0%
                      green: 0%
                      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

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:
                  - lambda: id(voice_asst).set_use_wake_word(true);
                  - voice_assistant.start_continuous
            - 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
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
            - script.execute: reset_led
  - platform: restart
    name: "${name} Restart"
    
light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO19 # GPIO33 for esp32-korvo-1.1 GPIO19 for esp32-s3-korvo-1
    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: 28%
              green: 100%
              blue: 90%
              num_leds: 12
          add_led_interval: 40ms
          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
      - addressable_color_wipe:
          name: "Thinking"
          colors:
            - red: 1%
              green: 90%
              blue: 99%
              num_leds: 2
            - red: 13%
              green: 17%
              blue: 87%
              num_leds: 6
          add_led_interval: 75ms
          reverse: true
      - 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%

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: speaker_volume
    type: int
    restore_value: yes
    initial_value: '50'  # Initial volume level (0-100)
button:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
    on_press:
      then:
        - lambda: |-
           if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_vol_up
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_vol_down
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
  - 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"

# Connection status
  - platform: status
    name: "${friendly_name} Status"

sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: GPIO8 # pin GPIO39 for esp32-korvo-1.1 GPIO8 for  esp32-s3-korvo-1
    attenuation: auto
    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_vol_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_vol_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_vol_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_vol_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
  
# Wifi signal
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
 # Generic volume sensor was used so it can be used in the esp_adf_speaker to get volume into HA
  - platform: template
    id: generic_volume_sensor 
    internal: true  

  - platform: template
    name: "${friendly_name} Volume"
    accuracy_decimals: 0
    id: scaled_volume_sensor
    lambda: |-
      // Scale the volume from 0-100 to 0-10
      return id(generic_volume_sensor).state / 10.0;

@schneipk
Copy link

@dwitgen s Code sample works for me on my V5.0 board. I get audio output from the headphone jack. Microphones are picking up sound and sending it to my Homeassistant instance. I can even switch wake words in HA. Thank you so much for posting, saved me some sleep deprived nights @dwitgen! 👍 🤟
Is the mic constantly streaming to HA? Or is the wake word (model or setting) copied to the device and wake word detection is on device?
Is it possible to reroute the speaker output to another media player in the network?
Or maybe even use the speaker of the Korvo for other output from HA?
THANK YOU GUYS!

@dwitgen
Copy link

dwitgen commented Dec 14, 2024

@dwitgen s Code sample works for me on my V5.0 board. I get audio output from the headphone jack. Microphones are picking up sound and sending it to my Homeassistant instance. I can even switch wake words in HA. Thank you so much for posting, saved me some sleep deprived nights @dwitgen! 👍 🤟 Is the mic constantly streaming to HA? Or is the wake word (model or setting) copied to the device and wake word detection is on device? Is it possible to reroute the speaker output to another media player in the network? Or maybe even use the speaker of the Korvo for other output from HA? THANK YOU GUYS!

Yes the mic is always streaming to HA as this is used for wake word, if you use on device wake word the mic will always be on but the stream is local for wake word detection. I had tried to get the on device wake word to work previously with no luck. When I was looking at my configs I seen the testing of on device wake word so I removed that from what I posted earlier. I cannot say about routing audio to another speaker. I had made some attempts at playing media to these and was able to get an https stream to partially work, it was very buggy and would likely take more skill than I have. There is also the possibility of multicast audio on esp32 which could then possibly used as an intercom again that would be quite a task to complete. I was able to get micro wake word to work. It is a little buggy as it does not like to switch back and forth correctly. It does seem to work if you change it then reset the device by the switch. This was tried on both the esp32-s3-korvo-1 and the esp32-korvo-1.1. Also had to modify the esphome micro_wake_word component. Mainly point to use the esp_adf_microphone used with the device instead of the esphome microphone. Not sure why but that was the only way I could get it to work. Also had an issue with the loop taking longer than 30ms and causing alot of errors so , made some changes to that as well. I will leave that as part of the repo but again I cannot guarantee it will be maintained or remain working. Below is the yaml for the esp32-s3-korvo-1 to work with on device wake word with comments for what is needed for the esp32-korvo-1.1 board:

substitutions:
  name: homeassist01
  friendly_name: HomeAssist Speaker 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 # current esphome wake word models are alexa,okay_nabu,hey_jarvis use one of these for micro_wake_word_model
 
esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"
  min_version: 2023.12.8
  platformio_options:
    board_build.flash_mode: dio
  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: esp32-s3-devkitc-1
  flash_size: 8MB
  framework:
    type: esp-idf
    version: recommended #4.4.6
    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"
            
    components:
      - name: esp32_s3_korvo_1_board
        source:
          type: git
          url: https://github.com/dwitgen/Korvo_AudioBoards
          ref: s3_korvo-1  # Use the "s3_Korvo-1" branch for ESP32-S3 model and use the "korvo-1" branch for the ESP32 model
        refresh: 0s
psram:
  mode: octal
  speed: 80MHz

external_components:
  - source:
      type: git
      url: https://github.com/dwitgen/esphome.git
      ref: main
    components: [ esp_adf, micro_wake_word ]
    refresh: 0s

micro_wake_word:
  id: wake_word
  models:
    - model: github://esphome/micro-wake-word-models/models/v2/${micro_wake_word_model}.json
  on_wake_word_detected:
    then:
      - delay: 300ms
      - voice_assistant.start: 
          id: voice_asst
          wake_word: !lambda return wake_word; 
            
# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret haspk01_api_key

ota:
  - platform: esphome
    password: !secret haspk01_ota_pwd

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

time:
  platform: homeassistant
  id: homeassistant_time

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: !secret haspk01_ip
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Homeassist-Smartspeaker-01"
    password: !secret haspk01_ap_pwd

captive_portal:

esp_adf:
  board: esp32s3korvo1 # required to ID board configs/settings, if korvo v1.1 use esp32korvo1 instead

speaker:
  - platform: esp_adf
    id: external_speaker
    
microphone:
  - platform: esp_adf
    id: external_mic

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 1
  auto_gain: 31dBFS
  volume_multiplier: 6.0
  vad_threshold: 1
  use_wake_word: false
  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: 0%
        green: 100%
        brightness: 50%
        effect: pulse
  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:
    - 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
      then:
        - wait_until:
            lambda: return !id(wake_word).is_running() && !id(voice_asst).is_running();
        - if:
            condition:
              lambda: return id(wake_word_engine_location).state == "On device";
            then:
              - lambda: ESP_LOGD("micro_wake_word", "Resetting micro wake word pipeline (On device)...");
              - micro_wake_word.stop
              - delay: 500ms
              - 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:
          - 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:
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
    - script.execute: reset_led

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:
            - wait_until:
                lambda: return !id(voice_asst).is_running();
            - micro_wake_word.stop
            - delay: 300ms
            - if:
                condition:
                  switch.is_off: mute
                then:
                  - lambda: id(voice_asst).set_use_wake_word(true);
                  - voice_assistant.start_continuous:
          else:
            - wait_until:
                lambda: return !id(wake_word).is_running();
            - voice_assistant.stop
            - delay: 300ms
            - lambda: id(voice_asst).set_use_wake_word(false);
            - micro_wake_word.start

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: 100%
                      red: 0%
                      green: 0%
                      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

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"
    
light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO19 # GPIO33 for esp32-korvo-1.1 GPIO19 for esp32-s3-korvo-1
    num_leds: 12
    rmt_channel: 0 # for the esp32-korvo-1.1 you can try 1, that almost seemed to be more stable
    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: 28%
              green: 100%
              blue: 90%
              num_leds: 12
          add_led_interval: 40ms
          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
      - addressable_color_wipe:
          name: "Thinking"
          colors:
            - red: 1%
              green: 90%
              blue: 99%
              num_leds: 2
            - red: 13%
              green: 17%
              blue: 87%
              num_leds: 6
          add_led_interval: 75ms
          reverse: true
      - 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%

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: speaker_volume
    type: int
    restore_value: yes
    initial_value: '50'  # Initial volume level (0-100)
button:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
    on_press:
      then:
        - lambda: |-
           if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_vol_up
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_vol_down
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
  - 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"

# Connection status
  - platform: status
    name: "${friendly_name} Status"

sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: GPIO8 # pin GPIO39 for esp32-korvo-1.1 GPIO8 for  esp32-s3-korvo-1
    attenuation: auto
    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_vol_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_vol_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_vol_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_vol_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
  
# Wifi signal
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
 # Generic volume sensor was used so it can be used in the esp_adf_speaker to get volume into HA
  - platform: template
    id: generic_volume_sensor 
    internal: true  

  - platform: template
    name: "${friendly_name} Volume"
    accuracy_decimals: 0
    id: scaled_volume_sensor
    lambda: |-
      // Scale the volume from 0-100 to 0-10
      return id(generic_volume_sensor).state / 10.0;

@dotorg
Copy link

dotorg commented Dec 16, 2024

Interesting -- so that does work on my unit, although not for long. Something seems to trip it up and it occasionally will try to restart the mic while the mic is in the process of stopping, it gets an error and never tries again. Not a big deal -- the normal ESP-ADF support works fine with the mic, anyway.

Looking through the branch, @dwitgen, can I ask why you re-implemented the 8311 support? Some of the code seems to be mostly cut-n-pasted from the ADF framework implementation, but because it wasn't forked from it, I can't tell what you needed to change. Since long-term getting a full ADF component working with this is better (both for long-term maintenance and so stuff like the media player is supported), I'm still trying to figure out what you're doing differently that works where the stock 8311 support doesn't. (I think there's an issue in ADF, as the 8311 support works fine on all my other boards but I do not get output even from any of the Espressif samples but I do from your implementation...)

@dwitgen
Copy link

dwitgen commented Dec 18, 2024

Interesting -- so that does work on my unit, although not for long. Something seems to trip it up and it occasionally will try to restart the mic while the mic is in the process of stopping, it gets an error and never tries again. Not a big deal -- the normal ESP-ADF support works fine with the mic, anyway.

Looking through the branch, @dwitgen, can I ask why you re-implemented the 8311 supports? Some of the code seems to be mostly cut-n-pasted from the ADF framework implementation, but because it wasn't forked from it, I can't tell what you needed to change. Since long-term getting a full ADF component working with this is better (both for long-term maintenance and so stuff like the media player is supported), I'm still trying to figure out what you're doing differently that works where the stock 8311 support doesn't. (I think there's an issue in ADF, as the 8311 support works fine on all my other boards but I do not get output even from any of the Espressif samples, but I do from your implementation...)

@dotorg So, this was and is all just testing At the time I was doing testing with the 8311 and esp_adf but that was not for esp_adf. I was just going back and forth with my testing is why it was in there and is not used at all. esp_adf handles the 8311 and 7210 chips in the back end I believe. The normal esp_adf support, I am assuming you mean this github://pr#5230. This should work as this pr has the esp32s3korvo1 board in the init.py. The changes I made to the esp_adf where to add the esp32korvo1 (not s3) board to the init.py and also made changes to the speaker to allow for volume controls. I was never able to get volume to function with the pr#5230, that may have been due to me not doing something correct in the yaml but I did get volume to work making changes to the esp_adf speaker code. Also changed how the pa was used. Again, not saying it is correct, but I disabled the PA on setup and enable the pa at speaker start then stop it after it is done. This seemed to help with speaker popping noises. No changes were done to the microphone. But with that being said there is some differences in the yaml between the esp32-s3-korvo and the esp32-korov 1.1 so I am going to paste that yaml seeing how this subject is for the esp32-korvo-v1.1 and not the esp32-s3-korvo-1.

@dwitgen
Copy link

dwitgen commented Dec 18, 2024

So because this subject is for the esp32-korvo-v1.1 I should have posted the yaml for that and not the esp32-s3-korvo-1. So here is the yaml for the esp32-korvo-v1.1 and again is just testing and may not work as desired but should play back audio from the speaker and headphone jack. This yaml is set to user the esp32-wrover-kit.

substitutions:
  name: homeassist03
  friendly_name: HomeAssist Speaker 3
  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}"
  min_version: 2024.5.5
  platformio_options:
    board_build.flash_mode: dio
    board_build.flash: 8MB
  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: 8MB
  framework:
    type: esp-idf
    version: recommended
    sdkconfig_options:
      CONFIG_IDF_TARGET_ESP32: y
      CONFIG_ESP32_SPIRAM_SUPPORT: y
      CONFIG_SPIRAM_SPEED_80M: y
      CONFIG_SPIRAM_MODE_OCT: y
      CONFIG_ESP32_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32_DATA_CACHE_64KB: y
      CONFIG_ESP32_DATA_CACHE_LINE_64B: y
      CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT: y
      CONFIG_AUDIO_BOARD_CUSTOM: y
            
    components:
      - name: esp32_korvo1_board
        source:
          type: git
          url: https://github.com/dwitgen/Korvo_AudioBoards
          ref: korvo-1  # Use the "s3_Korvo-1" branch for ESP32-S3 model and use the "korvo-1" branch for the ESP32 model
        refresh: 0s
           
external_components:
  - source:
      type: git
      url: https://github.com/dwitgen/esphome.git
      ref: main
    components: [ esp_adf ]
    refresh: 0s
 
# Enable logging
logger:

ota:
  - platform: esphome
    password: !secret haspk03_ota_pwd
    
# Enable Home Assistant API
api:
  encryption:
    key: !secret haspk03_api_key
      
text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP Address"

time:
  platform: homeassistant
  id: homeassistant_time

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: !secret haspk03_ip
 
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Homeassist-Smartspeaker-03"
    password: !secret haspk03_ap_pwd

captive_portal:

esp_adf:
  board: esp32korvo1 # required to ID board configs/settings, if esp32 s3 korvo use esp32s3korvo1 instead

speaker:
  - platform: esp_adf
    id: external_speaker
    
microphone:
  - platform: esp_adf
    id: external_mic

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 0
  auto_gain: 31dBFS
  volume_multiplier: 1.0
  vad_threshold: 1
  use_wake_word: false
  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: 0%
        green: 100%
        brightness: 50%
        effect: pulse
  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:
    - 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
        then:
          - wait_until:
              not:
                voice_assistant.is_running:
         
  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:
          - lambda: id(voice_asst).set_use_wake_word(true);
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false;
    - script.execute: reset_led

  on_client_disconnected:
    - lambda: id(voice_asst).set_use_wake_word(false);
    - voice_assistant.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: 100%
                      red: 0%
                      green: 0%
                      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

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:
                  - lambda: id(voice_asst).set_use_wake_word(true);
                  - voice_assistant.start_continuous
            - 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
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
            - script.execute: reset_led
  - platform: restart
    name: "${name} Restart"
  
light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO33 # GPIO33 for esp32-korvo-1.1 GPIO19 for esp32-s3-korvo-1
    num_leds: 12
    rmt_channel: 1
    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: 28%
              green: 100%
              blue: 90%
              num_leds: 12
          add_led_interval: 40ms
          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
      - addressable_color_wipe:
          name: "Thinking"
          colors:
            - red: 1%
              green: 90%
              blue: 99%
              num_leds: 2
            - red: 13%
              green: 17%
              blue: 87%
              num_leds: 6
          add_led_interval: 75ms
          reverse: true
      - 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%

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: speaker_volume
    type: int
    restore_value: yes
    initial_value: '50'  # Initial volume level (0-100)
button:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
    on_press:
      then:
        - lambda: |-
           if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_vol_up
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_vol_down
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
  - 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"

# Status de connection
  - platform: status
    name: "${friendly_name} Status"

sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 39 # pin GPIO39 for esp32-korvo-1.1 GPIO8 for  esp32-s3-korvo-1
    attenuation: auto
    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_vol_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_vol_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_vol_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_vol_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
  
# Wifi signal
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
 # Generic volume sensor was used so it can be used in the esp_adf_speaker to get volume into HA
  - platform: template
    id: generic_volume_sensor 
    internal: true  

  - platform: template
    name: "${friendly_name} Volume"
    accuracy_decimals: 0
    id: scaled_volume_sensor
    lambda: |-
      // Scale the volume from 0-100 to 0-10
      return id(generic_volume_sensor).state / 10.0;

@matze19999
Copy link

matze19999 commented Jan 21, 2025

@dwitgen I get this error while compiling in esphome:

Reading CMake configuration...
-- Found Git: /usr/bin/git (found version "2.39.5") 
-- git rev-parse returned 'fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).'
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /data/cache/platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /data/cache/platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /data/cache/platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Building ESP-IDF components for target esp32
Processing 1 dependencies:
[1/1] idf (5.1.5)
-- Configuring incomplete, errors occurred!
See also "/data/build/homeassist03/.pioenvs/homeassist03/CMakeFiles/CMakeOutput.log".

fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
CMake Error at /data/cache/platformio/packages/framework-espidf/tools/cmake/build.cmake:266 (message):
  Failed to resolve component 'jsmn'.
Call Stack (most recent call first):
  /data/cache/platformio/packages/framework-espidf/tools/cmake/build.cmake:308 (__build_resolve_and_add_req)
  /data/cache/platformio/packages/framework-espidf/tools/cmake/build.cmake:603 (__build_expand_requirements)
  /data/cache/platformio/packages/framework-espidf/tools/cmake/project.cmake:604 (idf_build_process)
  CMakeLists.txt:3 (project)



========================== [FAILED] Took 7.50 seconds ==========================

Do you have an idea how to fix / workaround this?

@dwitgen
Copy link

dwitgen commented Jan 21, 2025

@matze19999 Ignore the below, the latest esphome is a major change and they are using pioarduino and the configuration is quite different. I am not sure how you need to rollback but that is what I would suggest you do as the configuration as it sits will not work with the new esphome version.

> Processing 1 dependencies:
> [1/1] idf (5.1.5)
> -- Configuring incomplete, errors occurred!

The latest ESPHome has updated the recommended IDF to 5.1.5. Try changing the version in the yaml. I have not updated so I cannot say for sure it will work. There is an issue see here: esphome/wake-word-voice-assistants#42 there was a post in this to revert ESPHome back to 2024.11.3.

- RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
+ RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
esp32:
  board: esp-wrover-kit
  flash_size: 8MB
  framework:
    type: esp-idf
    version: recommended

to this

esp32:
  board: esp-wrover-kit
  flash_size: 8MB
  framework:
    type: esp-idf
    version: 4.4.8

@matze19999
Copy link

Thank you, I got it working with your hint:


substitutions:
  name: esp32krovo
  friendly_name: HomeAssist Speaker 3
  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"
  IDF_MAINTAINER: "matze19999"

esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"
  min_version: 2024.5.5
  platformio_options:
    board_build.flash_mode: dio
    board_build.flash: 8MB
  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: 8MB
  framework:
    type: esp-idf
    version: 4.4.8
    platform_version: 5.4.0
    sdkconfig_options:
      CONFIG_IDF_TARGET_ESP32: y
      CONFIG_ESP32_SPIRAM_SUPPORT: y
      CONFIG_SPIRAM_SPEED_80M: y
      CONFIG_SPIRAM_MODE_OCT: y
      CONFIG_ESP32_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32_DATA_CACHE_64KB: y
      CONFIG_ESP32_DATA_CACHE_LINE_64B: y
      CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT: y
      CONFIG_AUDIO_BOARD_CUSTOM: y
            
    components:
      - name: esp32_korvo1_board
        source:
          type: git
          url: https://github.com/dwitgen/Korvo_AudioBoards
          ref: korvo-1  # Use the "s3_Korvo-1" branch for ESP32-S3 model and use the "korvo-1" branch for the ESP32 model
        refresh: 0s
           
external_components:
  - source:
      type: git
      url: https://github.com/dwitgen/esphome.git
      ref: main
    components: [ esp_adf ]
    refresh: 0s
 
      
text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP Address"

time:
  platform: homeassistant
  id: homeassistant_time

esp_adf:
  board: esp32korvo1 # required to ID board configs/settings, if esp32 s3 korvo use esp32s3korvo1 instead

speaker:
  - platform: esp_adf
    id: external_speaker
    
microphone:
  - platform: esp_adf
    id: external_mic

voice_assistant:
  id: voice_asst
  microphone: external_mic
  speaker: external_speaker
  noise_suppression_level: 0
  auto_gain: 31dBFS
  volume_multiplier: 1.0
  vad_threshold: 1
  use_wake_word: false
  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: 0%
        green: 100%
        brightness: 50%
        effect: pulse
  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:
    - 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
        then:
          - wait_until:
              not:
                voice_assistant.is_running:
         
  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:
          - lambda: id(voice_asst).set_use_wake_word(true);
          - voice_assistant.start_continuous:
          - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
        else:
          - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
    - lambda: id(init_in_progress) = false;
    - script.execute: reset_led

  on_client_disconnected:
    - lambda: id(voice_asst).set_use_wake_word(false);
    - voice_assistant.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: 100%
                      red: 0%
                      green: 0%
                      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

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:
                  - lambda: id(voice_asst).set_use_wake_word(true);
                  - voice_assistant.start_continuous
            - 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
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
            - script.execute: reset_led
  - platform: restart
    name: "${name} Restart"
  
light:
  - platform: esp32_rmt_led_strip
    id: led_ring
    name: "${friendly_name} Light"
    pin: GPIO33 # GPIO33 for esp32-korvo-1.1 GPIO19 for esp32-s3-korvo-1
    num_leds: 12
    rmt_channel: 1
    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: 28%
              green: 100%
              blue: 90%
              num_leds: 12
          add_led_interval: 40ms
          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
      - addressable_color_wipe:
          name: "Thinking"
          colors:
            - red: 1%
              green: 90%
              blue: 99%
              num_leds: 2
            - red: 13%
              green: 17%
              blue: 87%
              num_leds: 6
          add_led_interval: 75ms
          reverse: true
      - 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%

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: speaker_volume
    type: int
    restore_value: yes
    initial_value: '50'  # Initial volume level (0-100)
button:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_volume_up
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }

  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_volume_down
    on_press:
      then:
        - lambda: |-
           if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }

binary_sensor:
  - platform: template
    name: "${friendly_name} Volume Up"
    id: btn_vol_up
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) < 100) {
              id(external_speaker).volume_up();
            }
  - platform: template
    name: "${friendly_name} Volume Down"
    id: btn_vol_down
    publish_initial_state : True
    on_press:
      then:
        - lambda: |-
            if (id(speaker_volume) > 0) {
              id(external_speaker).volume_down();
            }
  - 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"

# Status de connection
  - platform: status
    name: "${friendly_name} Status"

sensor:
  - id: button_adc
    platform: adc
    internal: true
    pin: 39 # pin GPIO39 for esp32-korvo-1.1 GPIO8 for  esp32-s3-korvo-1
    attenuation: auto
    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_vol_up
              state: ON
      - above: 0.65
        below: 0.92
        then:
          - binary_sensor.template.publish:
              id: btn_vol_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_vol_up
              state: OFF
          - binary_sensor.template.publish:
              id: btn_vol_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
  
# Wifi signal
  - platform: wifi_signal
    name: "${friendly_name} WiFi Signal"
    update_interval: 60s
 # Generic volume sensor was used so it can be used in the esp_adf_speaker to get volume into HA
  - platform: template
    id: generic_volume_sensor 
    internal: true  

  - platform: template
    name: "${friendly_name} Volume"
    accuracy_decimals: 0
    id: scaled_volume_sensor
    lambda: |-
      // Scale the volume from 0-100 to 0-10
      return id(generic_volume_sensor).state / 10.0;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests