Speaker with volume LED effects for volume control

I have been trying to make a small speaker with i2s and wanted to add some LED effects.
There are effects you can trigger on play, pause, idle, etc. But I wanted to display effect to “visualize” volume, when it changes.
I was not able to find anything, so I made my own with some help from folks on Discord:

esphome:
  name: esp32audio1
  friendly_name: Audio output 1

  on_boot: 
    then:
      - light.turn_on:
          id: ledRing
          red: 20%
          green: 0%
          blue: 0%
          effect: "Slow pulse"
      - media_player.volume_set:
          id: player1
          volume: 10%


esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  on_client_connected:
    - light.turn_off:
        id: ledRing

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  domain: !secret domain

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32Audio1 Fallback Hotspot"
    password: "y74r3wv9L3sM"

captive_portal:
    

web_server:
  port: 80
  include_internal: True
  log: True
  local: True
  version: 3

status_led:
  pin:
    number: GPIO15
    inverted: False


media_player:
  - platform: i2s_audio
    name: Player 1
    id: player1
    dac_type: external
    i2s_dout_pin: GPIO4
    mode: stereo
    i2s_audio_id: i2sAudio1

    on_state:
      - lambda:
          if (id(volInternal).state != id(player1).volume) {
            ESP_LOGD("volume","We have volume change from %f to %f", volInternal->state, player1->volume);
            id(ledRing).turn_on().set_effect("volume bar").perform();
          } else {
            ESP_LOGW("volume", "Volume was not changed!");
            int outState = id(player1).state;
            if(outState == 2) id(ledRing).turn_on().set_effect("Walk").perform();
            if(outState == 4) id(ledRing).turn_on().set_effect("TTS").perform();
            if((outState != 0) && (outState != 2) && (outState != 4)) id(ledRing).turn_off().perform();
          }
      - delay: 2s
      - lambda: 
          if (id(volInternal).state != id(player1).volume) {
            ESP_LOGD("volume","We have END of volume change from %f to %f", volInternal->state, player1->volume);
            id(volInternal).state = id(player1).volume;
            int outState = id(player1).state;
            if(outState == 2) id(ledRing).turn_on().set_effect("Walk").perform();
            if(outState == 4) id(ledRing).turn_on().set_effect("TTS").perform();
            if((outState != 2) && (outState != 4)) id(ledRing).turn_off().perform();
          }


i2s_audio:
  i2s_lrclk_pin: GPIO27
  i2s_bclk_pin: GPIO25
  id: i2sAudio1


light:
  - platform: neopixelbus
    type: GRB
    id: ledRing
    variant: ws2812
    num_leds: 9
    name: "LED Ring"
    pin: GPIO16
    default_transition_length: 0s
    method:
      type: esp32_rmt
      channel: 0
    effects:
      - pulse:
          name: "Slow Pulse"
          transition_length: 750ms
          update_interval: 250ms
          min_brightness: 50%
          max_brightness: 100%
      - pulse:
          name: "Fast Pulse"
          transition_length: 100ms
          update_interval: 100ms
          min_brightness: 50%
          max_brightness: 100%
      - addressable_color_wipe:
          name: "Walk"
          colors:
            - red: 22%
              green: 100%
              blue: 100%
              num_leds: 1
              gradient: true
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 3
          add_led_interval: 100ms
          reverse: false
      - addressable_color_wipe:
          name: "TTS"
          colors:
            - red: 50%
              green: 30%
              blue: 0%
              num_leds: 3
              gradient: true
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 6
          add_led_interval: 50ms
          reverse: true
      - addressable_lambda: 
          name: "volume bar"
          update_interval: 100ms
          lambda: |-
            int volLeds = 8 * (id(player1).volume + 0.08);
            // ESP_LOGD("volumeLEDS", "Current volume leds: %d", volLeds);
            it.all() = Color(200, 0, 0);
            it.range(0, 8 - volLeds) = Color(100, 100, 100);

button:
  - platform: restart
    name: "Restart device"


number:
  - platform: template
    name: internal volume
    id: volInternal
    internal: True
    min_value: 0
    max_value: 1
    step: 0.01
    initial_value: 0.5
    optimistic: True

What it does is:

  • when volume changes, for 2 seconds it switches the effect to volume bar. This will shine LEDs in red according current volume and the rest will shine white. After 2 seconds, the effect will reset to what it was before - even during playback
  • when status is play, it will show effect Walk
  • when status is announce (Text to speech is used from HA), the effect will switch to TTS

Sounds great, is there a video?

What about when stopped, skipping track, paused?

Paused is status 2, that means the effect will go away. Skipped track still means you are in play mode, meaning the play animation will continue.
And video hot yet, but I can make one :slight_smile:

1 Like

Cool. I have a couple of circular LEDs. Was thinking of making a little box with a rotary encoder, the LED and maybe a couple of buttons. Could be made to look like our other furniture, so would have significant other approval.