From 1f151a804e2aff9ae22645bcc70ab3cbda0f853f Mon Sep 17 00:00:00 2001 From: Commander1024 Date: Thu, 8 May 2025 09:30:01 +0200 Subject: [PATCH] Migrated Raspiaudio Muse Luxe to esp-idf-based voice sattelite/speaker --- automations.yaml | 20 +- esphome/raspiaudio-muse-luxe.yaml | 476 ++++++++++++++++++++++-------- 2 files changed, 366 insertions(+), 130 deletions(-) diff --git a/automations.yaml b/automations.yaml index afcb348..4e5e142 100644 --- a/automations.yaml +++ b/automations.yaml @@ -106,6 +106,7 @@ - media_content_type: provider media_content_id: media-source://tts/tts.piper?message=Gute+Nacht%2C+schlaf+gut. action: media_player.play_media + enabled: true - target: area_id: - wohnzimmer @@ -172,27 +173,28 @@ - id: '1623941937228' alias: Licht im Schlafzimmer zur Schlafenszeit einschalten description: Bei Beginn der empfohlenen Schlafenszeit. - trigger: - - platform: state - entity_id: input_text.sleep_as_android + triggers: + - entity_id: input_text.sleep_as_android to: time_to_bed_alarm_alert for: 00:05:00 - condition: + trigger: state + conditions: - condition: state entity_id: person.marcus_scholz state: home - action: - - service: light.turn_on - target: + actions: + - target: device_id: 68868390eda35e969ec60a13020f2407 data: {} - - service: tts.speak - data: + action: light.turn_on + - data: cache: true media_player_entity_id: media_player.raspiaudio_muse_luxe message: Ab ins Bett, Schlafenszeit. target: entity_id: tts.piper + action: tts.speak + enabled: false mode: single - id: '1623954512941' alias: Licht im Schlafzimmer zum Aufwachen einschalten diff --git a/esphome/raspiaudio-muse-luxe.yaml b/esphome/raspiaudio-muse-luxe.yaml index f15e07e..4b7480b 100644 --- a/esphome/raspiaudio-muse-luxe.yaml +++ b/esphome/raspiaudio-muse-luxe.yaml @@ -1,6 +1,12 @@ 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: @@ -12,6 +18,14 @@ ota: 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 @@ -26,125 +40,144 @@ wifi: esphome: name: ${name} friendly_name: ${friendly_name} + min_version: 2025.2.0 name_add_mac_suffix: false - project: - name: raspiaudio.muse-luxe - version: "1.0" - min_version: 2024.6.0 + platformio_options: + board_build.flash_mode: dio + board_build.arduino.memory_type: qio_opi on_boot: + priority: -100.0 then: - - media_player.volume_set: - id: luxe_out - volume: 50% + - lambda: id(phase) = 0; + - script.execute: update_led esp32: - board: esp-wrover-kit + board: esp-wrover-kit + flash_size: 4MB framework: - type: arduino + 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: - -i2c: - - id: i2c_bus - sda: GPIO18 - scl: GPIO23 - -dashboard_import: - package_import_url: github://esphome/media-players/raspiaudio-muse-luxe.yaml@main + level: DEBUG captive_portal: -external_components: - - source: github://pr#3552 # DAC support https://github.com/esphome/esphome/pull/3552 - components: [es8388] - refresh: 0s +improv_serial: +########## +# Hardware Configuration es8388: + id: my_es8388 -i2s_audio: - - id: i2s_audio_bus - i2s_lrclk_pin: GPIO25 - i2s_bclk_pin: GPIO5 +psram: + mode: quad + speed: 80MHz -media_player: - - platform: i2s_audio - name: None - id: luxe_out - dac_type: external - i2s_dout_pin: GPIO26 - mode: stereo - mute_pin: +####### +# Buses Configuration +i2c: + sda: GPIO18 + scl: GPIO23 + +##################### +# Internal Components +output: + - platform: gpio + id: dac_mute + pin: number: GPIO21 inverted: true + mode: + output: true -microphone: - - platform: i2s_audio - id: luxe_microphone - i2s_din_pin: GPIO35 - adc_type: external - pdm: false - -voice_assistant: - microphone: luxe_microphone - on_start: - - light.turn_on: - id: top_led - blue: 100% - red: 0% - green: 0% - effect: none - on_tts_start: - - light.turn_on: - id: top_led - blue: 60% - red: 20% - green: 20% - effect: none - on_tts_end: - - media_player.play_media: !lambda return x; - - light.turn_on: - id: top_led - blue: 60% - red: 20% - green: 20% - effect: pulse - on_end: - - delay: 1s - - wait_until: - not: - media_player.is_playing: luxe_out - - light.turn_off: top_led - on_error: - - light.turn_on: - id: top_led - blue: 0% - red: 100% - green: 0% - effect: none - - delay: 1s - - light.turn_off: top_led +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 - id: battery_sensor pin: GPIO33 - name: Battery - icon: "mdi:battery-outline" + name: Battery voltage device_class: voltage + unit_of_measurement: "V" + accuracy_decimals: 2 state_class: measurement entity_category: diagnostic - unit_of_measurement: V update_interval: 15s - accuracy_decimals: 3 - attenuation: 12db - raw: true + attenuation: auto filters: - - multiply: 0.00173913 # 2300 -> 4, for attenuation 11db, based on Olivier's code + - 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 @@ -155,10 +188,14 @@ binary_sensor: input: true pullup: true name: Volume Up - id: volume_up on_click: - - media_player.volume_up: - id: luxe_out + - 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 @@ -167,55 +204,252 @@ binary_sensor: input: true pullup: true name: Volume Down - id: volume_down on_click: - - media_player.volume_down: - id: luxe_out + - 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: Action - id: action_button - on_multi_click: - - timing: - - ON FOR AT MOST 350ms - - OFF FOR AT LEAST 10ms - then: - - media_player.toggle: - id: luxe_out - - timing: - - ON FOR AT LEAST 350ms - then: - - voice_assistant.start: - - timing: - - ON FOR AT LEAST 350ms - - OFF FOR AT LEAST 10ms - then: - - voice_assistant.stop: + 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: fastled_clockless + - platform: esp32_rmt_led_strip name: None id: top_led pin: GPIO22 - chipset: SK6812 + 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 -button: - - platform: safe_mode - id: button_safe_mode - name: Safe Mode Boot +i2s_audio: + i2s_lrclk_pin: GPIO25 + i2s_bclk_pin: GPIO5 + i2s_mclk_pin: GPIO0 - - platform: factory_reset - id: factory_reset_btn - name: Factory reset +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;