# ============================================================================= # GENERIC ESP32-S3 FULL AEC - Dual I2S bus, no codec # ============================================================================= # Generic target: ESP32-S3 board with PSRAM, one INMP441 microphone and one # MAX98357A amplifier on separate I2S buses. # # Tested hardware: Freenove ESP32-S3-WROOM CAM / FNK0085, camera module removed. # Tested chip/memory: ESP32-S3 N8R8, 8 MB flash, 8 MB OPI PSRAM. # Tested modules: INMP441 digital MEMS microphone, MAX98357A I2S amplifier. # # Audio pipeline: # INMP441 -> RX I2S bus -> esp_audio_stack (48 kHz) -> esp_ae_rate_cvt -> esp_aec -> VA / VoIP / MWW # Media/VA/VoIP -> mixer (48 kHz) -> esp_audio_stack -> TX I2S bus -> MAX98357A # # Pin choice rationale: # - Camera GPIOs are reused because the camera module is removed. # - Do not use GPIO35/36/37: OPI PSRAM on this Freenove N8R8 board. # - Do not use GPIO38/39/40: onboard SD card bus. # - Do not use GPIO19/20: native USB D+/D-. # - Do not use GPIO43/44: UART0 serial logging/programming. # - GPIO48 drives the onboard WS2812 status LED. # - Avoid GPIO0/3/45/46 for I2S clocks/data because they are boot/strap/log/JTAG-sensitive. # # Wiring: # INMP441: # SCK/BCLK -> GPIO9 # WS/LRCLK -> GPIO10 # SD/DOUT -> GPIO11 # L/R -> 3V3, tied locally with VDD so the mic outputs right channel # VDD -> 3V3 # GND -> GND # # MAX98357A: # BCLK -> GPIO12 # LRC/WS -> GPIO13 # DIN -> GPIO14 # SD/EN -> GPIO18 (optional; tie high instead if your board prefers) # VIN -> 5V or 3V3 according to your MAX98357A module # GND -> GND # # Sources checked: # - Freenove/Freenove_ESP32_S3_WROOM_Board: applies to FNK0085. # - Freenove ESP32S3_Pinout.png: camera pins 4/5/6/7/15/16/17/18/8/9/10/11/12/13/14, # SD pins 38/39/40, PSRAM pins 35/36/37, USB pins 19/20, WS2812 pin 48. # - PlatformIO board freenove_esp32_s3_wroom: N8R8, qio_opi, 8 MB flash, 8 MB PSRAM. # ============================================================================= # ============================================================================= # PROJECT SETUP # ============================================================================= substitutions: name: generic-s3-dual-aec friendly_name: Generic S3 Dual AEC 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/" mic_id: mic_main runtime_controller_led_preset: rgb_single # --------------------------------------------------------------------------- # Pinout: microphone I2S bus # --------------------------------------------------------------------------- rx_i2s_bclk_pin: GPIO9 rx_i2s_lrclk_pin: GPIO10 rx_i2s_din_pin: GPIO11 # --------------------------------------------------------------------------- # Pinout: speaker I2S bus # --------------------------------------------------------------------------- tx_i2s_bclk_pin: GPIO12 tx_i2s_lrclk_pin: GPIO13 tx_i2s_dout_pin: GPIO14 # --------------------------------------------------------------------------- # Pinout: speaker amplifier and optional status LED # --------------------------------------------------------------------------- speaker_enable_pin: GPIO18 status_led_pin: GPIO48 status_led_chipset: WS2812 status_led_rgb_order: GRB # Shared packages: full VA/VoIP runtime, runtime controller, phonebook sync, # dual-bus AEC audio, mixer/media player and diagnostics. 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 speaker_power: github://n-IA-hane/esphome-intercom/packages/esp_audio_stack/speaker_power_output.yaml@main flac_codecs_psram: github://n-IA-hane/esphome-intercom/packages/audio/flac_codecs_psram.yaml@main # ============================================================================= # BOARD AND MEMORY # ============================================================================= # Device identity exposed to ESPHome, HA and the shared VoIP phonebook. esphome: name: ${name} friendly_name: ${friendly_name} # ESP32-S3 target and ESP-IDF tuning for the no-codec dual-bus full profile. esp32: board: freenove_esp32_s3_wroom variant: esp32s3 flash_size: 8MB framework: type: esp-idf advanced: compiler_optimization: PERF sdkconfig_options: CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y" # Freenove exposes the S3 USB-Serial/JTAG port as /dev/ttyACM*. Keep # ESP-IDF stdout and ESPHome logger on the same port used for flashing. CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG: "y" CONFIG_ESP_CONSOLE_SECONDARY_NONE: "y" # OPI PSRAM gives VA, media, runtime controller and VoIP buffers headroom. psram: mode: octal 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: # 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 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 # ============================================================================= # OUTPUTS AND LIGHTS # ============================================================================= # Optional amplifier enable pin for dual-bus class-D boards. output: - platform: gpio id: speaker_enable pin: ${speaker_enable_pin} # ============================================================================= # AUDIO PIPELINE # ============================================================================= # Bus I2S: enables separate hardware backends for mic RX and speaker TX esp_audio_stack: id: audio_stack rx_bus: i2s_num: 0 i2s_bclk_pin: ${rx_i2s_bclk_pin} i2s_lrclk_pin: ${rx_i2s_lrclk_pin} i2s_din_pin: ${rx_i2s_din_pin} tx_bus: i2s_num: 1 i2s_bclk_pin: ${tx_i2s_bclk_pin} i2s_lrclk_pin: ${tx_i2s_lrclk_pin} i2s_dout_pin: ${tx_i2s_dout_pin} sample_rate: 48000 output_sample_rate: 16000 bits_per_sample: 32 slot_bit_width: 32 correct_dc_offset: true mic_channel: right rx_slot_mode: stereo processor_id: aec_processor aec_reference: previous_frame buffers_in_psram: true aec_ref_ring_in_psram: true audio_task_stack_in_psram: true # AEC: removes local speaker reference from the captured mic stream esp_aec: id: aec_processor sample_rate: 16000 filter_length: 4 mode: fd_high_perf # Microphone: publishes AEC-clean PCM frames to VA, wake word and VoIP microphone: - platform: esp_audio_stack id: mic_main esp_audio_stack_id: audio_stack # Speaker pipeline: sends mixed media, TTS and VoIP RX to the TX I2S bus 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: listens on the AEC-clean microphone stream micro_wake_word: microphone: mic_main models: - model: alexa # Voice Assistant: uses the same mic/speaker path as VoIP and media 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 phone: SIP endpoint, RTP media and local phonebook mirror voip_stack: task_stacks_in_psram: true buffers_in_psram: true 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 # Hardware and runtime switches exposed to HA. switch: # Runtime AEC mode selector. It restarts the local audio stack only while idle. select: - platform: template name: AEC Mode id: aec_mode_select icon: mdi:tune-vertical options: - "fd_low_cost" - "fd_high_perf" initial_option: "fd_high_perf" optimistic: false restore_value: true set_action: - if: condition: lambda: |- return id(init_in_progress) || x == id(aec_processor).get_mode_name(); then: - logger.log: level: DEBUG format: "AEC mode select: keeping current mode %s" args: ['x.c_str()'] 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 - lambda: |- id(aec_mode_select).publish_state(id(aec_processor).get_mode_name());