『瀚思彼岸』» 智能家居技术论坛

 找回密码
 立即注册
查看: 183|回复: 7

esp32 搓语音助手,好像跟官方的assist voice模式差不多,

[复制链接]

3

主题

44

帖子

398

积分

中级会员

Rank: 3Rank: 3

积分
398
金钱
354
HASS币
0
发表于 3 天前 | 显示全部楼层 |阅读模式
本帖最后由 hehe.1536 于 2025-2-18 18:56 编辑

  本人的环境,stt用的之前论坛里大佬分享的,跑在d525的openwrt的docker里,tts用的esge tts,对话代理用的是论坛里大佬的火天大有,home assistant第一代理,质谱轻言第二代理,

  用的硬件是esp32 s3 n16r8 ,麦克风INMP441,功放MAX98357A,喇叭随便找的,

大概原理是用开源的micro_wake_word做语音唤醒,唤醒后把录到的音上传给HA 的 conversation.process ,ha根据你的语音助手的设置开始处理,跟在手机上用ha的语音助手差不多了

  唤醒精度还行,灵敏度也够用,唤醒距离吧看使用环境,无噪音情况下在一个卧室里基本没问题,简单的指令走homeassistant的时候延迟大概五秒左右,走质谱轻言的话得十秒左右,有时会快,有时候会偏慢,感觉跟我跑stt的软路由性能有关系,手头设备好应该会快不少。edge tts好像也挺拉的

   指示灯啊各种使用细节的体验也就半成品,本人水平不够希望有大佬看到能给优化优化,要是有更成熟的方案也求指个路,找了好久都没找到,

【演示视频链接】 https://www.bilibili.com/video/B ... d9ec7ad304739133d75


esphome:
  name: esp32-voice-assist
  friendly_name: ESP32 Voice Assist
  platformio_options:
    board_build.flash_mode: dio

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

# Enable logging
logger:

ota:
  - platform: esphome
    password: "xxxx"

wifi:
  ssid: "xxxxxx"     ## wifi账号;
  password: "xxxxx"  ## wifi密码;

captive_portal:

web_server:  
  port: 80

# 启用 API(用于与 Home Assistant 交互)
api:
  encryption:
    key: "xxxxxxx"
  on_client_connected:
    then:
      - delay: 50ms
      - light.turn_off: led_status
      - micro_wake_word.start
  on_client_disconnected:
    then:
      - voice_assistant.stop

# LED 状态灯(可选,用于显示状态)
light:
  - platform: esp32_rmt_led_strip
    id: led_status
    pin: GPIO48
    num_leds: 1
    rgb_order: GRB
    rmt_channel: 0
    chipset: ws2812
    name: "状态灯"
    effects:
      - pulse:
          name: "Fast Pulse"
          transition_length: 0.5s
          update_interval: 0.5s
          min_brightness: 0%
          max_brightness: 100%

# I2S 音频输入和输出配置
i2s_audio:
  - id: i2s_in
    i2s_lrclk_pin: GPIO5    # INMP441的左/右时钟信号
    i2s_bclk_pin: GPIO6     # INMP441的比特时钟信号
  - id: i2s_out
    i2s_lrclk_pin: GPIO11   # MAX98357A的左/右时钟信号
    i2s_bclk_pin: GPIO12    # MAX98357A的比特时钟信号

# 麦克风配置(使用 I2S 数字麦克风,比如 INMP441)
microphone:
  - platform: i2s_audio
    id: mic
    adc_type: external
    i2s_din_pin: GPIO4    # INMP441的数据输入信号
    channel: left         # 使用左通道
    pdm: false            # 不使用脉冲密度调制(PDM)
    i2s_audio_id: i2s_in  # 使用 i2s_in 配置
    bits_per_sample: 32bit

# 扬声器配置(连接 MAX98357A)
speaker:
  platform: i2s_audio
  id: spk1
  i2s_audio_id: i2s_out
  dac_type: external
  i2s_dout_pin: GPIO13   # MAX98357A的数据输入信号
  channel: mono         # 使用单声道输出


# 语音唤醒配置,使用官方提供的预训练模型(此处用的是 okay_nabu 模型)
micro_wake_word:
  models:
    - model: "https://raw.githubusercontent.com/esphome/micro-wake-word-models/main/models/v2/okay_nabu.json"
  on_wake_word_detected:
    - voice_assistant.start
    - light.turn_on:
        id: led_status
        red: 30%
        green: 30%
        blue: 70%
        brightness: 60%
        effect: Fast Pulse

# 语音助手配置:通过麦克风采集音频,经 STT 转换后,将识别的文本传递给 HA 的对话系统
voice_assistant:
  id: va
  microphone: mic
  speaker: spk1
  noise_suppression_level: 2.0
  volume_multiplier: 4.0
  # 当 STT 结束后,将识别到的文本提交给 HA 的 conversation.process 服务
  on_stt_end:
    then:
      - homeassistant.service:
          service: conversation.process
          data:
            text: !lambda |-
              return x.c_str();
  on_error:
    then:
      - micro_wake_word.start
  on_end:
    then:
      - light.turn_off: led_status
      - wait_until:
          condition:
            - lambda: 'return !id(va).is_running();'
      - micro_wake_word.start



esphome编译的时候需要全局网络,我整的时候光电脑挂全局还不行,最后是在软路由上挂全局才完成的。


1. INMP441 麦克风接线

VCC(INMP441) —— 接 ESP32 的 3.3V 电源
GND(INMP441) —— 接 ESP32 的 GND
WS/LRCLK(INMP441) —— 接 ESP32 GPIO5
BCLK(INMP441) —— 接 ESP32 GPIO6
SD(INMP441) —— 接 ESP32 GPIO4
请注意:我这个麦克风是从以前做的小智ai上拆下来的,在那个项目里是要求麦克风的gnd和L/R短接的,所以我用的是短接的,用着正常,大家试的时候如果不行短接试试。

2. MAX98357A 扬声器接线

VIN(MAX98357A) —— 接 3.3V(或模块规定的供电电压)
GND(MAX98357A) —— 接 ESP32 的 GND
LRC/LRCLK(MAX98357A) —— 接 ESP32 GPIO11
BCLK(MAX98357A) —— 接 ESP32 GPIO12
DIN(MAX98357A) —— 接 ESP32 GPIO13

输出接口注意喇叭正负极





回复

使用道具 举报

1

主题

97

帖子

1051

积分

金牌会员

Rank: 6Rank: 6

积分
1051
金钱
954
HASS币
0
发表于 3 天前 | 显示全部楼层
感谢分享
回复

使用道具 举报

162

主题

2607

帖子

8074

积分

元老级技术达人

积分
8074
金钱
5462
HASS币
30
发表于 前天 00:18 | 显示全部楼层
效果视频也不发个
回复

使用道具 举报

0

主题

38

帖子

390

积分

中级会员

Rank: 3Rank: 3

积分
390
金钱
352
HASS币
0
发表于 前天 08:31 | 显示全部楼层
之前做过一个,根据外网代码改的,有快慢灯,可以显示状态。可以用,但是还有些问题,因为不太懂代码,后来就没再继续探索了。

substitutions:
  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'
  #name
  node_name: "Microphone"
  node_id: Mic
  node_name_friendly: "麦克风"
esphome:
  name: living-room-voice-assistant
  friendly_name: Living Room Voice Assistant
  
  on_boot:
    priority: 600
    then:
      - script.execute: control_led
      - delay: 30s
      - if:
          condition:
            lambda: return id(init_in_progress);
          then:
            - lambda: id(init_in_progress) = false;
            - script.execute: control_led

esp32:
  board: esp32dev
  framework:
    type: esp-idf


# Enable logging
logger:

# Enable Home Assistant API
packages:
  common: !include mic-common.yaml

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

captive_portal:

light:
  - platform: esp32_rmt_led_strip
    rgb_order: GRB
    pin: GPIO18
    num_leds: 3
    rmt_channel: 0
    chipset: WS2812
    name: "Status LED"
    id: led
    default_transition_length: 0s
    effects:
      - pulse:
          name: "extra_slow_pulse"
          transition_length: 800ms
          update_interval: 800ms
          min_brightness: 0%
          max_brightness: 30%
      - pulse:
          name: "slow_pulse"
          transition_length: 250ms
          update_interval: 250ms
          min_brightness: 50%
          max_brightness: 100%
      - pulse:
          name: "fast_pulse"
          transition_length: 100ms
          update_interval: 100ms
          min_brightness: 50%
          max_brightness: 100%
  
  #添加板载led查看状态  
  - platform: monochromatic
    name: "Board led"
    id: board_led
    restore_mode: ALWAYS_OFF
    default_transition_length: 0s
    effects:
      - pulse:
          name: "slow"
          transition_length: 200ms
          update_interval: 500ms
          min_brightness: 50%
          max_brightness: 100%
      - pulse:
          name: "fast"
          transition_length: 100ms
          update_interval: 100ms
          min_brightness: 50%
          max_brightness: 100%
    output: board_out_led

binary_sensor:
  # 板载的boot按键,有问题,无法使用
  - platform: gpio
    pin:
      number: GPIO0
      inverted: true
    name: "Board BOOT BUTTON"
    id: boot_button
    on_multi_click:
      - timing:
          - ON for at least 250ms
          - OFF for at least 50ms
        then:
          - if:
              condition:
                switch.is_off: use_wake_word
              then:
                - if:
                    condition: voice_assistant.is_running
                    then:
                      - voice_assistant.stop:
                    else:
                      - voice_assistant.start:
              else:
                - voice_assistant.stop
                - delay: 0.25s
                - voice_assistant.start_continuous:
            
output:
  - platform: ledc
    pin: GPIO2
    id: board_out_led

i2s_audio:
  - id: i2s_in
    i2s_lrclk_pin: GPIO25 #ws
    i2s_bclk_pin: GPIO26 #clk
  - id: i2s_out
    i2s_lrclk_pin: GPIO32
    i2s_bclk_pin: GPIO13

microphone:
  platform: i2s_audio
  id: external_microphone
  adc_type: external
  i2s_audio_id: i2s_in
  i2s_din_pin: GPIO34 #sd
  pdm: false
  bits_per_sample: 32bit


speaker:
  platform: i2s_audio
  id: external_speaker
  dac_type: external
  i2s_audio_id: i2s_out
  i2s_dout_pin: GPIO12
  mode: mono

voice_assistant:
  id: va
  microphone: external_microphone
  speaker: external_speaker
  use_wake_word: true
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.5



  on_listening:
    - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
    - script.execute: control_led

  on_stt_vad_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
        # change 2,add stop
    - voice_assistant.stop
    - script.execute: control_led

  on_tts_stream_start:
    - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
    - script.execute: control_led

  on_tts_stream_end:
    - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
    - script.execute: control_led
        # change 3, add delay and start
    - delay: 0.25s
    - voice_assistant.start

  on_error:
    - if:
        condition:
          lambda: return !id(init_in_progress);
        then:
          - lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};
          - script.execute: control_led
          - delay: 1s
          - if:
              condition:
                switch.is_on: use_wake_word
              then:
                - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
              else:
                - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
          - script.execute: control_led
        
     # change 4: restart voice assistant on any error
    - logger.log:
        format: "Error so stopping voice assistant"
        level: INFO
    - voice_assistant.stop:
    - delay: 1s
    - logger.log:
        format: "Re-starting voice assistant after error detected"
        level: INFO
    - voice_assistant.start

  on_client_connected:
    - if:
        condition:
          switch.is_on: use_wake_word
        then:
          - 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};
    - script.execute: control_led         

  on_client_disconnected:
    - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
    - script.execute: control_led

switch:
  - platform: template
    name: Use Wake Word
    id: use_wake_word
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    on_turn_on:
      - 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:
                  - voice_assistant.start_continuous
            - script.execute: control_led         

    on_turn_off:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - voice_assistant.stop
            - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
            - script.execute: control_led         

globals:
  - id: init_in_progress
    type: bool
    restore_value: no
    initial_value: 'true'
  - id: voice_assistant_phase
    type: int
    restore_value: no
    initial_value: ${voice_assist_not_ready_phase_id}
  
script:
  - id: control_led
    then:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - if:
                condition:
                    wifi.connected:
                then:
                  - if:
                      condition:
                          api.connected:
                      then:
                        - lambda: |
                            switch(id(voice_assistant_phase)) {
                              case ${voice_assist_listening_phase_id}:
                                id(led).turn_on().set_rgb(0, 0, 1).set_brightness(1.0).set_effect("none").perform();
                                id(board_led).turn_on().set_brightness(1.0).set_effect("none").perform();
                                break;
                              case ${voice_assist_thinking_phase_id}:
                                id(led).turn_on().set_rgb(0, 1, 0).set_effect("slow_pulse").perform();
                                id(board_led).turn_on().set_brightness(0.5).set_effect("slow").perform();
                                break;
                              case ${voice_assist_replying_phase_id}:
                                id(led).turn_on().set_rgb(0, 0, 1).set_brightness(1.0).set_effect("fast_pulse").perform();
                                id(board_led).turn_on().set_brightness(0.5).set_effect("fast").perform();
                                break;
                              case ${voice_assist_error_phase_id}:
                                id(led).turn_on().set_rgb(1, 1, 1).set_brightness(.5).set_effect("none").perform();
                                id(board_led).turn_on().set_brightness(0.5).set_effect("none").perform();
                                break;
                              case ${voice_assist_muted_phase_id}:
                                id(led).turn_off().perform();
                                id(board_led).turn_off().perform();
                                break;
                              case ${voice_assist_not_ready_phase_id}:
                                id(led).turn_on().perform();
                                id(board_led).turn_on().perform();
                                break;
                              default:
                                id(led).turn_on().set_rgb(1, 0, 0).set_brightness(0.2).set_effect("none").perform();
                                id(board_led).turn_on().set_brightness(0.2).set_effect("none").perform();
                                break;
                            }
                      else:
                        - light.turn_off:
                            id: led
                        - light.turn_off:
                            id: board_led
                else:
                  - light.turn_off:
                      id: led
                  - light.turn_off:
                      id: board_led                     
          else:
            - light.turn_on:
                id: led
                blue: 50%
                red: 50%
                green: 50%
                effect: "fast_pulse"
            - light.turn_off:
                id: board_led
回复

使用道具 举报

0

主题

9

帖子

56

积分

注册会员

Rank: 2

积分
56
金钱
47
HASS币
0
发表于 昨天 12:30 | 显示全部楼层
靠,科学了也下不了一些库
回复

使用道具 举报

3

主题

44

帖子

398

积分

中级会员

Rank: 3Rank: 3

积分
398
金钱
354
HASS币
0
 楼主| 发表于 昨天 21:42 | 显示全部楼层
bugensui 发表于 2025-2-21 00:18
效果视频也不发个

???我那个演示视频链接不能看?
回复

使用道具 举报

3

主题

44

帖子

398

积分

中级会员

Rank: 3Rank: 3

积分
398
金钱
354
HASS币
0
 楼主| 发表于 昨天 21:44 | 显示全部楼层
mying 发表于 2025-2-22 12:30
靠,科学了也下不了一些库

我是用pc的esphome编译的,你要不用电脑试试
回复

使用道具 举报

0

主题

9

帖子

56

积分

注册会员

Rank: 2

积分
56
金钱
47
HASS币
0
发表于 昨天 23:11 | 显示全部楼层
hehe.1536 发表于 2025-2-22 21:44
我是用pc的esphome编译的,你要不用电脑试试

Recv failure: Connection was reset  在github 拉代码,报这个错
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian

GMT+8, 2025-2-23 19:26 , Processed in 0.075302 second(s), 30 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表