substitutions: name: "raspiaudio-muse-luxe" friendly_name: "RaspiAudio Muse Luxe" #States P_starting: "0" P_waiting: "1" P_playing: "2" P_listening: "3" P_answering: "4" # Enable Home Assistant API api: encryption: key: !secret apikey ota: - platform: esphome id: ota_esphome password: !secret ota external_components: - source: github://RASPIAUDIO/esphomeLuxe@main # - source: # type: local # path: components components: [es8388] refresh: 0s wifi: ssid: Voltage-legacy password: !secret voltage_legacy_psk use_address: ${name}.home power_save_mode: high fast_connect: on # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Raspiaudio Fallback Hotspot" password: !secret fallback_psk esphome: name: ${name} friendly_name: ${friendly_name} min_version: 2025.2.0 name_add_mac_suffix: false platformio_options: board_build.flash_mode: dio board_build.arduino.memory_type: qio_opi on_boot: priority: -100.0 then: - lambda: id(phase) = 0; - script.execute: update_led esp32: board: esp-wrover-kit flash_size: 4MB framework: type: esp-idf version: recommended sdkconfig_options: CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y" CONFIG_ESP32S3_DATA_CACHE_64KB: "y" CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y" micro_wake_word: id: mww models: - model: https://github.com/kahrendt/microWakeWord/releases/download/okay_nabu_20241226.3/okay_nabu.json # - model: hey_jarvis # - model: hey_mycroft # - model: alexa # vad: microphone: luxe_mic on_wake_word_detected: - voice_assistant.start: wake_word: !lambda return wake_word; logger: level: DEBUG captive_portal: improv_serial: ########## # Hardware Configuration es8388: id: my_es8388 psram: mode: quad speed: 80MHz ####### # Buses Configuration i2c: sda: GPIO18 scl: GPIO23 ##################### # Internal Components output: - platform: gpio id: dac_mute pin: number: GPIO21 inverted: true mode: output: true globals: - id: Vol type: float initial_value: '0.6' - id: phase type: int initial_value: '0' - id: mute type: bool initial_value: 'false' sensor: - platform: adc pin: GPIO33 name: Battery voltage device_class: voltage unit_of_measurement: "V" accuracy_decimals: 2 state_class: measurement entity_category: diagnostic update_interval: 15s attenuation: auto filters: - multiply: 2 # https://forum.raspiaudio.com/t/esp-muse-luxe-bluetooth-speaker/294/12 - exponential_moving_average: alpha: 0.2 send_every: 2 - delta: 0.002 on_value: then: - sensor.template.publish: id: battery_percent state: !lambda "return x;" - platform: template name: Battery id: battery_percent device_class: battery unit_of_measurement: "%" accuracy_decimals: 0 state_class: measurement entity_category: diagnostic update_interval: 15s filters: - calibrate_polynomial: degree: 3 datapoints: - 4.58 -> 100.0 - 4.5 -> 97.1 - 4.47 -> 94.2 - 4.44 -> 88.4 - 4.42 -> 82.7 - 4.41 -> 76.9 - 4.41 -> 71.1 - 4.37 -> 65.3 - 4.35 -> 59.5 - 4.31 -> 53.8 - 4.28 -> 48.0 - 4.26 -> 42.2 - 4.23 -> 36.4 - 4.21 -> 30.6 - 4.19 -> 24.9 - 4.16 -> 19.1 - 4.1 -> 13.3 - 4.07 -> 10.4 - 4.03 -> 7.5 - 3.97 -> 4.6 - 3.82 -> 1.7 - 3.27 -> 0.0 - lambda: return clamp(x, 0.0f, 100.0f); binary_sensor: - platform: gpio pin: number: GPIO19 inverted: true mode: input: true pullup: true name: Volume Up on_click: - lambda: |- id(Vol) += 0.05; if(id(Vol) > 1) id(Vol) = 1; - media_player.volume_set: id: luxe_media_player volume: !lambda return id(Vol); - platform: gpio pin: number: GPIO32 inverted: true mode: input: true pullup: true name: Volume Down on_click: - lambda: |- id(Vol) -= 0.05; if(id(Vol) < 0) id(Vol) = 0; - media_player.volume_set: id: luxe_media_player volume: !lambda return id(Vol); - platform: gpio pin: number: GPIO12 inverted: true mode: input: true pullup: true name: Mute on_click: - if: condition: - lambda: 'return(id(mute));' then: - script.execute: mute_off - lambda: id(mute) = false; else: - script.execute: mute_on - lambda: id(mute) = true; on_double_click: - if: condition: - lambda: 'return(id(phase) == 2);' then: - media_player.stop: light: - platform: esp32_rmt_led_strip name: None id: top_led pin: GPIO22 chipset: WS2812 num_leds: 1 rgb_order: grb # rmt_channel: 0 default_transition_length: 0s gamma_correct: 2.8 effects: - pulse: name: pulse transition_length: 250ms update_interval: 250ms - pulse: name: slow_pulse transition_length: 1s update_interval: 2s i2s_audio: i2s_lrclk_pin: GPIO25 i2s_bclk_pin: GPIO5 i2s_mclk_pin: GPIO0 microphone: - platform: i2s_audio id: luxe_mic sample_rate: 16000 i2s_din_pin: GPIO35 bits_per_sample: 16bit channel: left adc_type: external speaker: - platform: i2s_audio id: luxe_speaker i2s_dout_pin: GPIO26 dac_type: external sample_rate: 48000 bits_per_sample: 16bit channel: stereo buffer_duration: 100ms media_player: - platform: speaker name: None id: luxe_media_player # volume_min: 0.5 # volume_max: 0.8 announcement_pipeline: speaker: luxe_speaker format: FLAC sample_rate: 48000 num_channels: 2 files: - id: little_sound file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/timer_finished.flac on_announcement: - micro_wake_word.stop: - if: condition: lambda: 'return(id(phase) != 2);' then: - lambda: |- if(id(phase) == 1) id(phase) = 2; - script.execute: mute_off - script.execute: update_led on_idle: - wait_until: and: - not: media_player.is_announcing: - not: voice_assistant.is_running: - if: condition: lambda: 'return((id(phase) == 4) || (id(phase) == 2));' then: - lambda: |- id(phase) = 1; - micro_wake_word.start: - script.execute: update_led voice_assistant: id: va microphone: luxe_mic media_player: luxe_media_player use_wake_word: false noise_suppression_level: 2 auto_gain: 31dBFS volume_multiplier: 2.0 on_listening: - logger.log: "listening 3 => phase" - micro_wake_word.stop: - lambda: |- id(phase) = 3; - script.execute: update_led on_stt_end: - media_player.play_media: !lambda return x; - light.turn_on: id: top_led blue: 60% red: 20% green: 20% effect: pulse on_tts_start: - logger.log: "answering 4 => phase" - lambda: |- id(phase) = 4; - script.execute: update_led on_error: - logger.log: "ERROR!!!!!!!!!!!!!!!!" - light.turn_on: id: top_led blue: 0% red: 100% green: 0% effect: pulse - delay: 3s - lambda: id(phase) = 1; - script.execute: update_led ######### # Scripts script: - id: update_led then: - logger.log: "==>>>update_led" - lambda: |- if(id(phase) == 0)id(start).execute(); if(id(phase) == 1)id(waiting).execute(); if(id(phase) == 2)id(external_player).execute(); if(id(phase) == 3)id(listening).execute(); if(id(phase) == 4)id(answering).execute(); - id: start then: - light.turn_on: id: top_led effect: slow_pulse red: 80% green: 0% blue: 80% - delay: 5sec - lambda: id(my_es8388).setup(); - output.turn_off: dac_mute - lambda: id(phase) = 1; - media_player.speaker.play_on_device_media_file: media_file: little_sound announcement: true - script.execute: update_led - id: waiting then: - light.turn_on: id: top_led effect: pulse red: 0% green: 0% blue: 100% brightness: 100% - voice_assistant.stop: - micro_wake_word.start: - id: listening then: - light.turn_on: id: top_led effect: pulse red: 0% green: 100% blue: 0% brightness: 100% - id: answering then: - light.turn_on: id: top_led effect: none red: 100% green: 100% blue: 0% brightness: 100% - id: external_player then: - light.turn_on: id: top_led effect: none red: 80% green: 40% blue: 0% - id: mute_on then: - media_player.volume_set: volume: '0' - lambda: id(mute) = true; - id: mute_off then: - media_player.volume_set: volume: !lambda return(id(Vol)); - lambda: id(mute) = false;