Ws2812b 16bit ring as volume display

Im trying to make a esphome module (esp12S on breakout board) that included a 16 bit ws2812b ring (16 pcs led) to show the volume of my mediaplayer.

I was thinking of making a service on esphome config, that will accept a value of 0-100 for the volume in procent.
Then the esp module has to do the math to light up the individual leds to display the volume, ex. 50% volume turns on 8 pcs of leds. 75% 12 pcs and 100% all of them.

I have been reading the docs but cant figure out how to do this. I want the light to be internal, so the service should only be available for hass,
I think I have to go the lambda route, but I need some guidelines.

What I can do, is the service and accepting a value.
Light to control the ws2812 ring (all led turns on and off with same color).

Think you have to look here :slight_smile:

(PS: I might have to steal the idea ;))

Yeah, I was looking at it before, but dont know how to pass the volume value into the effects.
And if effects really is the way to go here.

for ex.

!lambda |-
          // The sensor outputs values from 0 to 100. The blue
          // part of the light color will be determined by the sensor value.
          return id(some_sensor).state / 100.0;

id(some_sensor).state how would I write to get the value sent into the service component on the esphome?
can it be as easy as setting a ID on the service, and then call id(service_id).my_brightness (using this name from the example below).

I used this example to create a service

api:
  services:
    - service: start_effect
      variables:
        my_brightness: int
        my_effect: string
      then:
        - light.turn_on:
            id: my_light
            brightness: !lambda 'return my_brightness;'
            effect: !lambda 'return my_effect;'

Steal away :slight_smile:

Oh, just realized we some ‘else’ variables are needed too :wink: Otherwise it wont turn off when adjusting the volume down.

That looks like a lot of code :slight_smile: , was hoping for something more like ceil(16*(volumevariable/100)) to get the range_to and having range_from: 0

Your code looks like I will pull the state from hass and not pushing to esp ?

But will try once im home and see what can be done.

The volume state is pushed instantly to the ESP using the Home Assistant API.

I agree, it’s not pretty, and I would like something simpler. I’m also struggling with the light.addressable_set command - not doing anything for me.

Ok, tested some, but think the way to go is via a service on esphome
But it appears that addressable_set wont execute?
I added light.turn_on before addressable_set to test if something is running, that is executed and changes the light. But light.addressable_set wont do anything…

Any ideas?

api:
  password: 'xx'
  
  services:
    - service: volume_level
      variables:
        volume: float
      then:
        - light.turn_on:
            id: volumelevel
            red: 80%
            green: 100%
            blue: 0%
        - light.addressable_set:
            id: volumelevel
            range_from: 0
            range_to: 3
            red: 100%
            green: 0%
            blue: 0%

light:
  - platform: fastled_clockless
    id: volumelevel
    chipset: WS2812B
    rgb_order: GRB
    pin: GPIO2
    num_leds: 16
    name: "Volume level"

I’ve created an issue on GitHub: https://github.com/esphome/issues/issues/568

Ah, ok, so it might be esphome then.
I’ll put this little project on hold for a bit then.

Thanks for the help so far.

I have it working, but the “code” ain’t pretty :wink:

Can’t see the gif sorry.

For those who’re following this thread I’ve sent a PR to fix this bug.

3 Likes

Tested the 1.14.0-dev version including the fix @glmnet added.
It work!

This is how I made it work. A made the service accepting volume value (I use node-red to adjust the volume), and sending that will trigger the light.addressable_set.
The lambda do the math of what leds to turn on (here I have to try to fix it, instead of hard coding 16 as total leds I need to get a variable or the value from the light config…)

The only issue I have now with this is that I have to turn on the leds manually, if lights are off and I send the light.addressable_set it wont do anything, should it not turn on the leds if I send ex. red: 100% ?
Do I have to add something more?

api:  
  services:
    - service: disp_volume
      variables:
        volume: float
      then:
        - light.addressable_set:
            id: volumelevel
            range_to: !lambda 'return floor(16*(volume/100));'
            red: 100%
            green: 0%
            blue: 0%
        - light.addressable_set:
            id: volumelevel
            range_from: !lambda 'return ceil(16*(volume/100));'
            red: 0%
            green: 0%
            blue: 100%

Did you try
- light.turn_on: ...
Before doing the addressable set ?

You really have more maths in there than you need.

16*(volume/100)

is the same as

0.16*volume

But as all volumes in home assistant are expressed as a float between 0.00 and 1.00 - ie 50% volume is 0.50, so I am puzzled why you have them as a number between 0 and 100 in the first place.

Yes, I tested this out now, but that went weird.
For some reason the light.addressable_set got more or less overwritten by the light.turn_on or it is called the same time and as light.turn_on has 1s transition it takes over I think, I tested with triggering it multiple times, and notices a little flashing of the LEDs.

I added this if statement, but still, it turns the leds on if they are off, but then I have to trigger the disp_volume again to set the correct leds… (ps. had the IF statement before all addressable_set but the same result)

api:
  services:
    - service: disp_volume
      variables:
        volume: float
      then:
        - light.addressable_set:
            id: volumelevel
            range_to: !lambda 'return floor(16*(volume/100));'
            red: 100%
            green: 0%
            blue: 0%
        - light.addressable_set:
            id: volumelevel
            range_from: !lambda 'return ceil(16*(volume/100));'
            red: 0%
            green: 0%
            blue: 100%
        - if:
            condition:
              light.is_off: volumelevel
            then:
              - light.turn_on: volumelevel

eh, yes :slight_smile:
Some times I get blind of how simple it could be.

About the volume, in node-red when doing a call I get the state of the chromecast, that has a volume value of 0-100 so I went with that logic.

no. sorry… I looked at it again… You are correct its 0-1 for volume!

Cool. I am following this as I have already set up a manual volume control with a rotary encoder (there is nothing like a big volume knob, everyone understands that!).

I think coupled with a ring for visual feedback, this will be great. Need to design a case/stand for it. I have something in mind :slight_smile:

OOOH!!
That would be awesome, having like a big heavy lathed metal knob.
This is the fun stuff with automation, you can do what ever you want/need :smiley: