TTS Speaker

Hi.
Have someone made their own tts-speaker that works with HA, using Raspberry Pi or similar?
I know that I could use google mini/Amazon Echo/Sonos as speaker. But in this case is not what Im looking for. I only need a speaker that play the tts when activated by HA. So if someone would like to share their project I will appreciate it.

I made several speakers with an esp32-S3. They are easy to make.




I will look for the yaml

1 Like
  • CPU: ESP32-S3 N16R8
  • Amp: MAX98357A
  • Led: The onboard LED is used
substitutions:
  name: "espeaker"
  friendly_name: ESPeaker

  esp_speaker_connect_sound: "http://x.x.x.x:8123/local/sounds/esp_speaker_started.mp3"

# ================================
# === Please do not edit below ===
# ================================
  music_player_idle_id: "1"
  music_player_playing_id: "2"
  music_player_muted_id: "3"
  esp_speaker_not_ready_phase_id: "10"
  esp_speaker_error_phase_id: "11"


esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: AA_van_Zoelen.ESPeaker
    version: '1.1.0'
  on_boot:
    priority: 600
    then:
      - script.execute: control_led
      - delay: 30s
      - if:
          condition:
            lambda: return id(init_in_progress);
          then:
            - lambda: id(init_in_progress) = false;
            - script.execute: control_led


esp32:
  board: esp32-s3-devkitc-1
  variant: esp32s3
  framework:
    type: arduino


psram:
  mode: quad # Please change this to quad for N8R2 and octal for N16R8
  speed: 80MHz


globals:
  - id: init_in_progress
    type: bool
    restore_value: false
    initial_value: "true"
  - id: esp_speaker_phase
    type: int
    restore_value: false
    initial_value: ${esp_speaker_not_ready_phase_id}
  - id: played_startup
    type: bool
    restore_value: false
    initial_value: "false"

logger:


api:
  encryption:
    key: !secret api_key 
  on_client_connected:
    - script.execute: control_led
  on_client_disconnected:
    - script.execute: control_led

ota:
  platform: esphome


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  on_connect:
    - script.execute: control_led
  on_disconnect:
    - script.execute: control_led

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret fallback_ssid
    password: !secret fallback_password


captive_portal:


i2s_audio:
  - id: i2s_audio_bus
    i2s_lrclk_pin: GPIO6
    i2s_bclk_pin: GPIO7


media_player:
  - platform: i2s_audio
    id: media_out
    name: ESP Speaker
    dac_type: external
    i2s_audio_id: i2s_audio_bus
    i2s_dout_pin: GPIO8
    mode: stereo #mono
    on_play:
      - logger.log: "Playback started!"
      - lambda: id(esp_speaker_phase) = ${music_player_playing_id};
      - if:
         condition:
           switch.is_on: status_led
         then:
           - script.execute: control_led     
    on_pause:
      - logger.log: "Playback paused!"
      - lambda: id(esp_speaker_phase) = ${music_player_muted_id};
      - if:
         condition:
           switch.is_on: status_led
         then:  
           - script.execute: control_led         
    on_idle:
      - logger.log: "Playback finished!"
      - lambda: id(esp_speaker_phase) = ${music_player_idle_id}; 
      - if:
         condition:
           switch.is_on: status_led
         then: 
           - script.execute: control_led


switch:
  - platform: template
    name: "Status LED"
    id: status_led
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON
    icon: mdi:bell
    on_turn_off:
    - light.turn_off:
        id: onboard_led
    on_turn_on:
    - script.execute: control_led


light:
  - platform: esp32_rmt_led_strip
    id: onboard_led
    rgb_order: GRB
    pin: GPIO48
    num_leds: 1
    rmt_channel: 0
    chipset: ws2812
    effects:
      - pulse:
          name: "Slow Pulse"
          transition_length: 250ms
          update_interval: 250ms
          min_brightness: 60%
          max_brightness: 80%
      - pulse:
          name: "Fast Pulse"
          transition_length: 100ms
          update_interval: 100ms
          min_brightness: 60%
          max_brightness: 80%
      - pulse:
          name: "Playing"
          min_brightness:  20%
          max_brightness: 40%
          transition_length: 3s      # defaults to 1s
          update_interval: 3s


script:
  - id: control_led
    then:
      - if:
          condition:
            lambda: return !id(init_in_progress);
          then:
            - if:
                condition:
                  wifi.connected:
                then:
                  - if:
                      condition:
                        api.connected:
                      then:
                        - lambda: |
                            switch(id(esp_speaker_phase)) {
                              case ${music_player_idle_id}:
                                id(media_player_idle_state).execute();
                                break;
                              case ${music_player_playing_id}:
                                id(media_player_playing_state).execute();
                                break;
                              case ${music_player_muted_id}:
                                id(media_player_muted_state).execute();
                                break;                                                                
                              case ${esp_speaker_not_ready_phase_id}:
                                id(control_led_esp_speaker_not_ready_phase).execute();
                                break;                                
                              case ${esp_speaker_error_phase_id}:
                                id(control_led_esp_speaker_error_phase).execute();
                                break;
                              default:
                                break;
                            }
                      else:
                        - script.execute: control_led_no_ha_connection_state
                else:
                  - script.execute: control_led_no_ha_connection_state
          else:
            - script.execute: control_led_init_state

  # Media player stated playing displayed via the on board LED
  - id: media_player_playing_state
    then:
      - light.turn_on:
          id: onboard_led
          blue: 5%
          red: 5%
          green: 0%
          brightness: 5%
          effect: "Playing"  

  # Media player stated muted displayed via the on board LED
  - id: media_player_muted_state
    then:
      - light.turn_on:
          id: onboard_led
          blue: 60%
          red: 0%
          green: 0%
          brightness: 65%
          effect: "Fast Pulse"  

  # Media player stated idle displayed via the on board LED
  - id: media_player_idle_state
    then:
      - light.turn_on:
          id: onboard_led
          blue: 0%
          red: 15%
          green: 15%
          brightness: 15%
          effect: "None"  

  # Script executed during initialisation
  - id: control_led_init_state
    then:
      - light.turn_on:
          id: onboard_led
          blue: 0%
          red: 0%
          green: 100%
          brightness: 50%
          effect: "Fast Pulse"      
      - if:
         condition:
           lambda: return !id(played_startup);
         then:
           - media_player.volume_set:
               id: media_out
               volume: 50%
           - media_player.play_media: ${esp_speaker_connect_sound}
           - lambda: id(played_startup) = "true";


  # Script executed when the device has no connection to Home Assistant
  - id: control_led_no_ha_connection_state
    then:
      - light.turn_on:
          id: onboard_led
          blue: 0%
          red: 50%
          green: 0%
          brightness: 50%
          effect: "Slow Pulse"

  # Script executed when the esp speaker is not ready
  - id: control_led_esp_speaker_not_ready_phase
    then:
      - light.turn_off:
          id: onboard_led

  # Script executed when the esp speaker encounters an error        
  - id: control_led_esp_speaker_error_phase
    then:
      - light.turn_on:
          id: onboard_led
          blue: 0%
          red: 100%
          green: 0%
          brightness: 98%
          effect: "none"


# Diagnosics
sensor:
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"

  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifi_signal_db
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
    device_class: ""

  - platform: uptime
    name: Uptime Sensor    
# Diagnosics
button:
  - platform: restart
    id: "restart_device"
    name: "Restart"
    entity_category: "diagnostic"
#  - platform: safe_mode
#    id: "restart_device_safe_mode"
#    name: "Restart (Safe Mode)"
#    entity_category: "diagnostic"
# Diagnosics
text_sensor:
  # Expose WiFi information as sensors.
  - platform: wifi_info
    ip_address:
      name: IP
    ssid:
      name: SSID
    bssid:
      name: BSSID

Nice!
Think I will give it a try. Do you also have some recommendation for speaker to use?
Guess a 4ohm speaker at max 2-3watt?
Is it something else that I should be aware of?

thanks in advance and for that you sharing your project :slight_smile:

A small speaker in a small housing will give you a flimsy sound. That said make sure you have a reasonable volume in your housing. Also make the outside sturdy. So when using a 3W 4-8ohm speaker the sound will have more body to it. More bass.

The last picture is a very small speaker and it will mainly produce mid and high tones while the other are larger and have also bass tones

The last one is a 3D resin print
The cilindercal version is just a pvc pipe
The round one is made from 2 bamboe snack bowls. The bottle on top contains ferrofluid and it ‘dance’ on the ritme of the music. This one is the largest. The speaker comes from a old pc speaker box bought for €5,- at a 2nd hand store