
YAML-конфігурація ESP32-S3-Box-3: аудіо-дуplex, дзвінок слухачу та «Стоп» як слово зупинки для голосової допомоги!
https://ift.tt/HeTStMx
Audio duplex, Listening Chime, and ‘Stop’ halt word and for voice Assist!
(Переглянути результат тут перед читанням всього цього)
У вас є кілька ESP32-S3-BOX-3 і вам здається, що вам не вистачає команди «Стоп», щоб пристрій перестав відповідати? Або хотілося б, щоб він «дзвонив», коли він чує будильник wake word?
Як ви, можливо, вже знаєте, це пов’язано з відсутністю нативного Audio duplex для них у ESPHome. Це означає, що аудіо не може відтворюватися, поки мікрофон активно слухає.
Це має багато недоліків, таких як відсутність «Звуку активації» при запуску пристрою, щоб уникнути того, що мікрофон повинен чекати завершення, перш ніж слухати. Також це створює головну проблему — відсутність слова ‘Halt’, яке могло б одразу зупинити відповідь.
Це означає, що ви не зможете сказати «Стоп», щоб пристрій зупинив відповідь, як це роблять Home Assistant VPES.
У цьому невеликому виступі я покажу, як я інтегрував власний компонент, щоб усунути це обмеження!
Поки я шукати широко й довкола для customization-компонента, який міг би принести це моїм 9 ESPBoxes, ніщо не з’являлося під час моїх пошуків… До того часу, поки я не знайшов ESPHOME-Intercom, який відповів на мою потребу досить досконало, оскільки це включає користувацький компонент i2s_audio_duplex для міжкімнатних систем, щоб мати аудіо-дуплекс.
Якщо ви просто хочете швидко це зробити без читання презентації, ось повний YAML-файл.
Мінімальні вимоги:
– ESP32-S3-BOX-3
– ESPHome
– Розроблений потік Assist
Користувацькі компоненти, які використано:
– ESPHOME-Intercom (Credit: n-IA-hane)
– i2s_audio_duplex, esp_aec, audio_processor
І тепер, без зайвих слів, Гід
1) Перший крок: локально завантажити проект ESPHOME-Intercom і зберегти його на вашій машині, наприклад під /config/esphome/external/ на вашому хості Home-Assistant.
2) Додайте це до YAML для ваших ESP32-Box:
external_components:
# Локальна копія n-IA-hane/esphome-intercom @ v4.0.0
– source:
type: local
path: /config/esphome/external/esphome-intercom-4.0.0/esphome/components
components: [i2s_audio_duplex, esp_aec, audio_processor]
Чому локально?
HA-розширення постійно вичерпується під час фази пакетної індексації git. Завантаження tarball v4.0.0 поза пристрою та розміщення його під /config/esphome/external/ пропускає реальний крок git під час виконання.
i2s_audio_duplex: Один шина, обидві напрямки одночасно
Дуплекс-компонент замінює штатний i2s_audio на такий, що відкриває периферію I2S у режимі master TX+RX. Один спільний LRCLK / BCLK / MCLK обслуговує як ADC, так і DAC, з окремими лініями даних DIN / DOUT.
Потрібно повністю видалити верхній елемент ‘i2s_audio’ і замінити його на:
i2s_audio_duplex:
id: i2s_duplex
i2s_lrclk_pin: GPIO45
i2s_bclk_pin: GPIO17
i2s_mclk_pin: GPIO2
i2s_din_pin: GPIO16 # мікрофон увін
i2s_dout_pin: GPIO15 # динамік вивід
sample_rate: 48000
output_sample_rate: 16000
slot_bit_width: 32
bits_per_sample: 16
correct_dc_offset: true
processor_id: aec_processor
buffers_in_psram: true
aec_reference: previous_frame
Чому використовується посилання на previous_frame?
AEC повинен знати, що зараз відтворює динамік. Використання фактичного DMA-кадру з попереднього періоду I2S дає точно вирівняний опорний сигнал — без зайвого буферизації, без дрейфу тактов, без програмного лупбека.
Чому два частоти дискретизації?
Апаратному пристрою подобається 48 кГц (чистий відтвор DAC). Wake word та STT працюють на 16 кГц. Компонент зменшує дискретизацію на виході з мікрофонного тракту, щоб MWW і VA бачили очікувану швидкість.
esp_aec: Акустичне шумозаглушення у мікрофонному конвеєрі
esp_aec реєструється як processor_id на дуплекс-компоненті. Кожен кадр мікрофона проходить через нього разом із останнім кадром від динаміка — результат: чистий запис, що ігнорує власний TTS пристрою.
Необхідно додати цей блок коду перед попереднім (i2s_audio_duplex).
esp_aec:
id: aec_processor
sample_rate: 16000
filter_length: 4
mode: sr_low_cost
#i2s_audio_duplex:
# id: i2s_duplex
# i2s_lrclk_pin: GPIO45
# <...
# ...>
Що це відкриває: будильник-звоном, відповідь TTS та навіть відтворення медіа більше не повторно виявляються за допомогою micro_wake_word або вигукуються в STT-запит. Мікрофон залишається «відкритим» увесь час.
Додаємо аудіо адаптери під блок [i2s_audio_duplex]:
#i2s_audio_duplex:
# id: i2s_duplex
# <...
# ...>
# buffers_in_psram: true
# aec_reference: previous_frame
audio_adc:
– platform: es7210
id: es7210_adc
bits_per_sample: 16bit
sample_rate: 48000
audio_dac:
– platform: es8311
id: es8311_dac
bits_per_sample: 16bit
sample_rate: 48000
microphone:
– platform: i2s_audio_duplex
id: box_mic
i2s_audio_duplex_id: i2s_duplex
speaker:
– platform: i2s_audio_duplex
id: box_speaker
i2s_audio_duplex_id: i2s_duplex
sample_rate: 48000
Друга модель MWW, прихована від Home Assistant
micro_wake_word:
id: mww
microphone: box_mic
stop_after_detection: false
models:
– model: okay_nabu
– model: https://ift.tt/CVn9Iv0
id: stop
internal: true
probability_cutoff: 0.40
sliding_window_size: 3
vad:
probability_cutoff: 0.20
sliding_window_size: 1
Чому internal: true?
Home Assistant за замовчуванням вимикає не-головні wake-слова у своїх перемикачах по конвеєру. internal: true приховує модель від HA повністю, тож вона завантажується на рівні прошивки і завжди доступна. Ми повторно включаємо її при кожному завантаженні, повторному підключенні HA та після кожного переключення TTS (у start_wake_word).
Чому розмірні пороги більші?
За замовчуванням поріг ймовірності (0.5) та вікно з 5 кадрів додають близько 50 мс затримки, що помітно, коли ви намагаєтеся перервати під час речення. 0.40 / 3 кадри потрапляють у вікно середнього підтвердження «Stop» і спрацьовують приблизно через 30 мс після початку.
Секція «The speaker-pause hack…» — хак зупинки, який дійсно зупиняє аудіо
Наївна послідовність «зупинити все» — voice_assistant.stop + media_player.stop — не мовчить динамік. Відстеження джерела дуплексу показало, чому: media_player має чергу даних, speaker.play() повторно реєструє слухача, і станова машина переходить STOPPED → STARTING → parent.start_speaker(). Аудіо відтворюється знову за кілька мілісекунд.
script:
– id: global_cancel
then:
– lambda: id(user_cancelled) = true;
– voice_assistant.stop:
– media_player.stop:
announcement: true
– media_player.stop: speaker_media_player
– lambda: |-
id(i2s_duplex).set_speaker_paused(true);
id(i2s_duplex).stop_speaker();
– switch.turn_off: timer_ringing
– script.execute: set_idle_or_mute_phase
– script.execute: draw_display
set_speaker_paused(true)
Обхід стану машини. process_tx_path_ memsets вихідний буфер нулями кожного кадру під час паузи, тож DMA відтворює лише тишу незалежно від того, що черговий буфер оголошення або media_player продовжують подавати.
stop_speaker()
Запитує скидання буфера, щоб вже queued PCM був скинутий, а не відкладений. Флаг паузи знімається voice_assistant.on_listening під час наступної черги, тож будильник-звоном та нова відтворення TTS йдуть нормально.
Відоме обмеження
media_player може застрягти в ANNOUNCING
ESPHome’s audio_reader має жорстко закодований бюджет повторів (6 × 5 с = 30 с) на fetch_headers і не може бути скасований ззовні. Коли користувач скасовує під час TTS, потік HTTP ще в польоті; media_player.stop лише позначає запит на зупинку. До того, як верхня лінія потоку помилиться, VA не може залишити STREAMING_RESPONSE і новий раунд не може початися.
Щоб пом’якшити проблему: динамік залишається призупиненим (безшумно) і на LCD відображається страница «заняті», щоб користувач бачив, що пристрій живий. Нові раунди приймаються, як тільки верхній потік помиляється. Сам збої не можна виправити з YAML — це частина ядра ESPHome.
Опціональні елементи сенсорного екрану
Я особисто додав керування сенсорним екраном до своїх ESP32-Box.
Повна зона торкання (320×240) розміщена поверх LVGL-сторінок. Вона обирає один з двох шляхів залежно від того, чим займається асистент.
Що це розблоковує: дозволяє швидко зупинити або ініціювати асистента простим натисканням пальця будь-де на екрані!
binary_sensor:
– platform: touchscreen
name: “Screen Touch”
touchscreen_id: board_touchscreen
x_min: 0
x_max: 320
y_min: 0
y_max: 240
on_press:
then:
– if:
condition:
or:
– lambda: |-
const int phase = id(voice_assistant_phase);
return phase == ${voice_assist_listening_phase_id} ||
phase == ${voice_assist_thinking_phase_id} ||
phase == ${voice_assist_replying_phase_id};
– and:
– media_player.is_announcing:
– lambda: ‘return !id(user_cancelled);’
then:
– script.execute: global_cancel
else:
– if:
condition:
and:
– lambda: return id(voice_assistant_phase) == ${voice_assist_idle_phase_id};
– switch.is_off: mute
– not:
voice_assistant.is_running:
then:
– lambda: id(va).set_use_wake_word(false);
– micro_wake_word.enable_model: stop
– voice_assistant.start:
Чому не зупиняємо micro_wake_word тут:
за старої напів-дуplex-архітектури MWW потрібно було зупинити перед тим, як VA виймає мікрофон. Разом із i2s_audio_duplex + AEC обидва одночасно використовують box_mic — залишати MWW увімкненим під час торкання зупинки дозволяє зберегти «Стоп» та панель «Okay Nabu» активними під час відповіді TTS.
Що змінилося з точки зору користувача
Сценарії:
– Сказати «Okay Nabu» під час відтворення TTS
– Розмовляти «Stop» під час відповіді
– Сказати «Stop» під час простою
– Торкнути екран під час простою
– Торкнути екран під час відповіді
– Торкнути під час дзвінкової будильникової паузи
– Wake-chime + STT після пробудження
Кожна з таблиць порівнює «до» та «після» з intercom.
Я також додав 3 демо-відео тут: https://ift.tt/EY5KpCl
І, звісно, дайте знати, якщо виникнуть проблеми під час повторення! Код протестований на 9 ESP32-S3-Box-3 і всі вони працюють.
Примітка про disclaimer та використання Claude:
Цей допис частково створений за допомогою Claude.
1 пост — 1 учасник
Читайте повну тему тут: https://ift.tt/HeTStMx
Hi-Fi News
через Home Assistant Community – останні теми: https://ift.tt/ytvT8j3
9 травня 2026, 20:19 (за українським часом)
May 9, 2026 at 08:19PM

Залишити відповідь
Щоб відправити коментар вам необхідно авторизуватись.