# ============================================================================= # GENERIC ESP32-S3 VOIP + VA AEC LIGHT - Single I2S bus, no hardware codec # ============================================================================= # Full-featured lightweight config: VoIP + Media Player + mixer + VA/MWW. # For ESP32 boards where mic and speaker share the SAME I2S bus. # # Audio pipeline: # MEMS mic → esp_audio_stack (48kHz) → esp_ae_rate_cvt x3 → esp_aec → mic_main → VA / VoIP TX / MWW # # Media → media_speaker_input (resampler) ────────────┐ # VA TTS → tts_speaker_input (resampler) ──────────────┼── mixer (48kHz) → hw_speaker → class-D amp # VoIP RX → voip_speaker_input (resampler) ─────────────┘ # # HARDWARE (adapt GPIOs to YOUR board): # - Board: Any ESP32-S3 with PSRAM # - Flash: 8 MB or larger. Full AEC + VA + VoIP + runtime controller no # longer fits the default 4 MB OTA app partition. # - Microphone: Any I2S MEMS mic on DIN pin # - Speaker: Any I2S class-D amp on DOUT pin # - Mic and speaker SHARE BCLK + LRCLK (same I2S bus, full-duplex) # - LED: SK6812/WS2812 RGB (optional) # - ESP32-S3R8/S3R8V: do not use PSRAM pins for I2S/LED; the GPIOs below # are examples only and may need moving on those modules. # # GPIO PINOUT (example, change to match your wiring): # Shared I2S: BCLK=GPIO36, LRCLK=GPIO37, DIN=GPIO35 (mic), DOUT=GPIO7 (speaker) # LED: DATA=GPIO33 # # ============================================================================= # ============================================================================= # PROJECT SETUP # ============================================================================= substitutions: # Hardware-shared identity: dropping firmware-variant suffix keeps build cache # and HA device entity stable across rebuilds. name: generic-s3 friendly_name: Generic S3 ext_components_source: "github://n-IA-hane/esphome-intercom@main" voip_stack_components_source: "github://n-IA-hane/esphome-voip-stack@main" audio_stack_components_source: "github://n-IA-hane/esphome-audio-stack@main" runtime_controller_components_source: "github://n-IA-hane/esphome-runtime-controller@main" assets_base: "https://github.com/n-IA-hane/esphome-intercom/raw/main/" # Microphone ID for package mute switch mic_id: mic_main runtime_controller_led_preset: rgb_single # --------------------------------------------------------------------------- # Pinout: shared I2S bus # --------------------------------------------------------------------------- # BCLK/LRCLK are shared by mic and speaker on this full-duplex bus. i2s_bclk_pin: GPIO36 i2s_lrclk_pin: GPIO37 # --------------------------------------------------------------------------- # Pinout: microphone # --------------------------------------------------------------------------- i2s_din_pin: GPIO35 # --------------------------------------------------------------------------- # Pinout: speaker # --------------------------------------------------------------------------- i2s_dout_pin: GPIO7 # --------------------------------------------------------------------------- # Pinout: optional status LED # --------------------------------------------------------------------------- status_led_pin: GPIO33 status_led_chipset: WS2812 status_led_rgb_order: GRB packages: full_voice_voip_runtime: github://n-IA-hane/esphome-intercom/packages/presets/full_voice_voip_runtime.yaml@main runtime_controller: github://n-IA-hane/esphome-runtime-controller/packages/runtime_controller/full_controller.yaml@main voice_assistant_runtime_controls: github://n-IA-hane/esphome-intercom/packages/voice_assistant/runtime_controls.yaml@main call_settings: github://n-IA-hane/esphome-intercom/packages/voip/call_settings.yaml@main ha_api: github://n-IA-hane/esphome-intercom/packages/voip/ha_api_runtime.yaml@main voice_assistant_local_commands: github://n-IA-hane/esphome-intercom/packages/voice_assistant/local_commands.yaml@main ota_maintenance: github://n-IA-hane/esphome-intercom/packages/ota/full_audio_maintenance.yaml@main voip_transport: github://n-IA-hane/esphome-intercom/packages/voip/transport.yaml@main phonebook_subscribe: github://n-IA-hane/esphome-intercom/packages/voip/phonebook_subscribe.yaml@main speaker_mute: github://n-IA-hane/esphome-intercom/packages/voip/speaker_mute_runtime.yaml@main s3_base: github://n-IA-hane/esphome-intercom/packages/platform/esp32s3_base.yaml@main native_diagnostics: github://n-IA-hane/esphome-intercom/packages/diagnostics/native.yaml@main status_led: github://n-IA-hane/esphome-intercom/packages/led/status_ws2812_single.yaml@main speaker_mixer: github://n-IA-hane/esphome-intercom/packages/audio/shared_mono_mixer_48k.yaml@main speaker_media_player: github://n-IA-hane/esphome-intercom/packages/media_player/runtime_controller_mono_media_player_48k.yaml@main va_timers: github://n-IA-hane/esphome-intercom/packages/voice_assistant/timers_runtime.yaml@main audio_stack_controls: github://n-IA-hane/esphome-intercom/packages/esp_audio_stack/controls.yaml@main aec_controls: github://n-IA-hane/esphome-intercom/packages/esp_audio_stack/aec_controls.yaml@main # ============================================================================= # BOARD AND MEMORY # ============================================================================= # Device identity exposed to ESPHome, HA and the shared VoIP phonebook. esphome: name: ${name} friendly_name: ${friendly_name} platformio_options: board_build.flash_mode: dio # ESP32-S3 target and ESP-IDF tuning for the no-codec full-experience profile. esp32: board: esp32-s3-devkitc-1 variant: esp32s3 flash_size: 8MB framework: type: esp-idf advanced: compiler_optimization: PERF sdkconfig_options: # Allow mbedtls to allocate its working buffers in PSRAM. CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y" # Route ESP-IDF stdout (and ESPHome logger when hardware_uart=USB_SERIAL_JTAG) # to the built-in USB-Serial-JTAG peripheral. Required on boards where the # USB-C connector is wired to the S3's native USB-Serial-JTAG (e.g. Freenove, # bare ESP32-S3-DevKitC-1) rather than to an external USB-UART chip. CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG: "y" CONFIG_ESP_CONSOLE_SECONDARY_NONE: "y" # PSRAM gives VA, media, runtime controller and VoIP buffers room on generic S3. psram: mode: quad speed: 80MHz # Persist user settings without excessive flash writes. preferences: flash_write_interval: 5min # ============================================================================= # EXTERNAL COMPONENTS # ============================================================================= # Project components for full VA/VoIP, runtime controller and no-codec AEC. external_components: - source: ${voip_stack_components_source} components: [voip_stack] - source: ${ext_components_source} components: [speaker, voice_assistant] - source: ${runtime_controller_components_source} components: [runtime_controller] - source: ${audio_stack_components_source} components: [esp_audio_stack, esp_aec] # ============================================================================= # CONNECTIVITY # ============================================================================= # Normal Wi-Fi client mode. Power save is disabled for low-latency SIP/RTP and # reliable HA API updates. wifi: ssid: !secret wifi_ssid password: !secret wifi_password power_save_mode: NONE # Home Assistant native API for entities, service actions and phonebook sync. api: # OTA update endpoint used by ESPHome Dashboard and `esphome upload`. ota: - platform: esphome # USB Serial/JTAG logger. VoIP and audio domains stay visible at INFO; noisy # entity domains stay WARN for normal field use. logger: hardware_uart: USB_SERIAL_JTAG # DEBUG keeps SIP signaling + AEC/AFE/i2s lifecycle visible. # Mute chatty ESPHome built-ins below; project components stay at DEBUG. level: INFO logs: voip_stack: INFO voip_stack.fsm: INFO voip_stack.tcp: INFO voip_stack.udp: INFO voip_stack.audio: INFO voip_stack.settings: INFO sensor: WARN text_sensor: WARN binary_sensor: WARN switch: WARN number: WARN button: WARN api: WARN api.connection: WARN component: WARN api.service: INFO light: INFO select: INFO speaker: INFO audio: INFO micro_wake_word: INFO voice_assistant: INFO media_player: INFO resampler: INFO mixer_speaker: INFO wifi: INFO json: INFO # ============================================================================= # AUDIO PIPELINE # ============================================================================= esp_audio_stack: id: audio_stack i2s_lrclk_pin: ${i2s_lrclk_pin} i2s_bclk_pin: ${i2s_bclk_pin} i2s_din_pin: ${i2s_din_pin} i2s_dout_pin: ${i2s_dout_pin} sample_rate: 48000 output_sample_rate: 16000 slot_bit_width: 32 correct_dc_offset: true processor_id: aec_processor # Lightweight no-codec AEC reference. This full AEC preset opts into # previous_frame so the Espressif/ADF TYPE2 ring-buffer path is not compiled. # Remove this line or set ring_buffer for canonical TYPE2 software reference. aec_reference: previous_frame buffers_in_psram: true esp_aec: id: aec_processor sample_rate: 16000 filter_length: 4 mode: fd_high_perf microphone: - platform: esp_audio_stack id: mic_main esp_audio_stack_id: audio_stack speaker: - platform: esp_audio_stack id: hw_speaker esp_audio_stack_id: audio_stack sample_rate: 48000 bits_per_sample: 16 timeout: 1s # ============================================================================= # MEDIA, ASSIST AND VOIP # ============================================================================= micro_wake_word: microphone: mic_main models: - model: alexa voice_assistant: microphone: mic_main media_player: speaker_media_player micro_wake_word: mww noise_suppression_level: 2 auto_gain: 31dBFS volume_multiplier: 2.0 voip_stack: task_stacks_in_psram: true # save ~28KB internal heap on S3 by placing task stacks in PSRAM buffers_in_psram: true # VoIP staging buffers in PSRAM; AEC/AFE buffers live in esp_audio_stack audio: # Primary/preferred RTP profile: 48 kHz/10 ms toward the speaker, and a # matching 10 ms TX reframe of the 16 kHz AEC mic for peers that negotiate # ptime 10. 32 kHz/16 ms remains an alternate advertised format. tx_formats: - sample_rate: 16000 pcm_format: s16le channels: 1 frame_ms: 10 rx: sample_rate: 48000 pcm_format: s16le channels: 1 frame_ms: 10 # Additional accepted RX formats for direct ESP-to-ESP calls. These are valid # only because speaker: voip_speaker_input points at the resampler; do # not advertise additional formats when wiring voip_stack directly to a # fixed-rate mixer input. rx_formats: - sample_rate: 32000 pcm_format: s16le channels: 1 frame_ms: 16 - sample_rate: 16000 pcm_format: s16le channels: 1 frame_ms: 10 - sample_rate: 16000 pcm_format: s16le channels: 1 frame_ms: 16 - sample_rate: 16000 pcm_format: s16le channels: 1 frame_ms: 32 microphone: mic_main speaker: voip_speaker_input ringing_timeout: 30s delete_contact_missing_from: updates_number: 1 # ============================================================================= # RUNTIME LOGIC AND ENTITIES # ============================================================================= # Local BOOT button: short press controls VA/call toggle, double press changes # contact, long press toggles microphone mute. binary_sensor: # BOOT button (GPIO0), multi-click - platform: gpio name: Boot Button id: boot_button pin: number: GPIO0 inverted: true mode: input: true pullup: true on_multi_click: # Single click: call toggle if VoIP call active, otherwise VA start/stop - timing: - ON for at most 500ms - OFF for at least 400ms then: - if: condition: not: - voip_stack.is_idle: then: - voip_stack.call_toggle: else: - runtime_controller.event: id: runtime event: wake_word # Double click: next contact - timing: - ON for 50ms to 500ms - OFF for at most 300ms - ON for 50ms to 500ms - OFF for at least 400ms then: - voip_stack.next_contact: # Long press (1-3s): toggle mute - timing: - ON for 1s to 3s - OFF for at least 300ms then: - switch.toggle: mute switch: select: - platform: template name: AEC Mode id: aec_mode_select icon: mdi:tune-vertical # Keep runtime switching within the full-duplex AEC engine. options: - "fd_low_cost" - "fd_high_perf" initial_option: "fd_high_perf" # Non-optimistic: HA reflects the live component mode, not the value the # user clicked. If the switch is rejected (e.g. internal RAM fragmented) # or the component falls back to the previous mode, the select shows the # actual mode rather than a fictitious one. optimistic: false restore_value: true set_action: - if: condition: lambda: 'return id(init_in_progress);' then: - logger.log: "AEC mode select: skipping during boot" else: - if: condition: or: - voice_assistant.is_running: - not: voip_stack.is_idle: then: - logger.log: level: WARN format: "Cannot switch AEC mode during active VA/VoIP" else: - logger.log: format: "Switching AEC mode to %s" args: ['x.c_str()'] - micro_wake_word.stop: - esp_audio_stack.stop_and_wait: audio_stack - esp_aec.set_mode: id: aec_processor mode: !lambda 'return x;' - esp_audio_stack.start: audio_stack - delay: 300ms - if: condition: switch.is_off: mute then: - micro_wake_word.start: - script.execute: boost_mww_priority # Reflect actual live mode (covers rejection and silent hardware mode). - lambda: |- id(aec_mode_select).publish_state(id(aec_processor).get_mode_name());