LED Strip to show power consumption

great piece of work but i got some questions
I have several sensors available for use and couple of them are a negative input when i’m using power or are negative when i’m injecting my surplus of solar power to the grid, is there a way to also display the injection?
I tried to adapt for the negative part myself but i must be like missing something somewhere.
besides that is it possible to work from both sides of the strip? Because I also have a batterysystem connected to my solar inverter and it would be nice to display the state of charge also…
So if i got a strip of 100 leds if i could use like 50 for power usage/injection and the other 50 for displaying the battery status.
This is what i got for the moment:

alias: Energy Usage LED
description: Display a meter bar for the house energy usage
trigger:
  - platform: state
    entity_id:
      - sensor.p1_meter_3c39e7243e44_active_power
action:
  - service: rest_command.wled_update
    data:
      payload: "{\"on\": true}"
  - service: rest_command.wled_update
    data:
      payload: >-
        {##### INPUTS #####} {# Use the value of the entity that has triggered
        the automation #} {% set sensor_entity_id = trigger.entity_id %} {% set
        sensor_entity_id = "sensor.p1_meter_3c39e7243e44_active_power" %} {#
        Value that should fill the complete meter #} {% set max_value = 5000 %}
        {# Number of leds in the strip #} {% set led_count = 100 %} {#####
        CALCULATION #####} {# Fraction of the meter that should be on #} {% set
        sensor_value = states(sensor_entity_id) | float(default=0) %} {% set
        fraction = sensor_value / max_value %} {% set brightness = 100 %} {# LED
        parts: normal for the active bar, then highlight the exact value, then
        lowlight the inactive part #} {% set highlight_end_leds = (fraction *
        led_count) | int %} {% set normal_end_leds = highlight_end_leds - 3 %}
        {% set lowlight_start_leds = highlight_end_leds %} {# Resulting segments
        to send to WLED #} {# - Start with turning everything off #} {% set
        result = namespace(segments=[0, led_count, [0,0,0]]) %} {##### HELPERS
        #####} {# Add a segment, while a possible scaledown in brightness #} {#
        - Reversed start/end because strip is upside down #} {% macro
        segment(start_led, end_led, color, scaledown=1) -%}
          {%
            set result.segments = result.segments + [
              led_count - end_led,
              led_count - start_led,
              [
                (color[0] / scaledown) | round(0, 'ceil'),
                (color[1] / scaledown) | round(0, 'ceil'),
                (color[2] / scaledown) | round(0, 'ceil'),

              ]
            ]
          %}
        {%- endmacro %} {# List of color blocks, references sensor values #} {%
          set blocks = [
            {"start": -2000, "end": 0, "color": [0, 0, 255]},
            {"start": 0, "end": 1000, "color": [0, 255, 0]},
            {"start": 1000, "end": 2500, "color": [255, 255, 0]},
            {"start": 2500, "end": max_value, "color": [255, 0, 0]},
          ]
        %} {% for block in blocks %}
          {# Convert sensor values of the block to led counts #}
          {% set block_start_leds = (block.start / max_value * led_count) | round %}
          {% set block_end_leds = (block.end / max_value * led_count) | round %}
          {# NORMAL segment #}
          {% if normal_end_leds >= block_start_leds %}
            {{
              segment(
                [block_start_leds, 0] | max,
                [block_end_leds, normal_end_leds] | min,
                block.color,
                scaledown = 1
              )
            }}
          {% endif %}
          {# HIGHLIGHT segment: If the highlight overlaps this block, add a highlight #}
          {% if ([block_start_leds, normal_end_leds] | max) <= ([block_end_leds, highlight_end_leds] | min) %}
          {{
            segment(
              [block_start_leds, normal_end_leds] | max,
              [block_end_leds, highlight_end_leds] | min,
              block.color,
              scaledown = 1
            )
          }}
          {% endif %}
          {# LOWLIGHT segment: when a part of this block is inactive #}
          {% if lowlight_start_leds <= block_end_leds %}
            {{
              segment(
                [block_start_leds, lowlight_start_leds] | max,
                [block_end_leds, led_count] | min,
                block.color,
                scaledown = 100
              )
            }}
          {% endif %}
        {% endfor %} {# Print results, passing it to the rest command #} {# -
        Use full segment brightness, but lower strip brightness #} {# - Turn
        on/off based on living room usage #} {{
          {
            "bri": brightness,
            "seg": {
              "bri": 255,
              "i": result.segments
            }
          }
        }}
mode: restart

Thanks in advance for any input!

My template/script assumes that the sensor entity has a range of 0 until max_value (5000 for your case). I have updated it to automatically scale the range based on the blocks configuration, see my latest version (or the diff of the changes). Note that the values used for the scaledown argument of the segment() calls is quite different in this new version as well, this is to adapt to changes in WLED, so either update that or update those values.

With these changes your power usage is still not shown great though, if the value is positive it will still highlight the complete -2000 to 0 part of the strip.

Maybe a good approach to either get a bar from 0 downwards for power generation, or a bar from 0 upwards for power usage would be to effectively render 2 of these meters on the same strip. Something like this:

  1. Meter for power usage:
    • led_count = 50
    • only the 0 till 5000 sensor values in the blocks list
    • remove the inversion of the bar in the set result.segments... block by removing the two led_count - parts
  2. Meter for power generation:
    • led_count = 50
    • Shift to Led 51 until 100 (add 50 to the led_count - end_led and led_count - start_led lines in the set result.segments ... block)
    • only the -2000 till 0 sensor value part in the blocks list

It should be possible to do by simply having 2 whole rest_command.wled_update service call blocks in the automation, both affecting only a part of the leds in the strip.

If the above works, you can use the same approach for using a part of the strip for displaying battery status, just rendering another meter and splitting the leds across them differently (25 power usage, 25 power generation, 50 battery stats).

Let me know if that helps!

Hello, I would like to use this to display the current PV output, I am new to HA, come from FHEM, have 2 systems running at the moment, I have implemented this accordingly on FHEM.
Unfortunately, the code below does not work, or the LED strip lights up permanently on orange/yellow (standard color) full scale.

Do I have to blueprint ect. use before?
I don’t get any errors at the moment, but nothing happens.

blueprint:
  name: Energy Usage LED
  description: Display a meter bar for the house energy usage
  domain: automation

alias: Energy Usage LED
description: Display a meter bar for the house energy usage
mode: single
id: energy_usage_light_bar # To enable debug
trigger:
  # Update when the energy usage changes
  - platform: state
    entity_id: sensor.total_dc_power
action:
  # - alias: "Turn on when living room in use, else turn off"
  #   # choose:
  #   sequence:
  - service: rest_command.wled_update
    data:
      payload: '{"on": true}'
  - service: rest_command.wled_update
    data:
      payload: >-
        {##### INPUTS #####}
        {# Use the value of the entity that has triggered the automation #}
        {% set sensor_entity_id = trigger.entity_id %}
        {% set sensor_entity_id = "sensor.total_dc_power" %}
        #{# Value that should fill the complete meter #}
        #{% set max_value = 100 %}
        {# Number of leds in the strip #}
        {% set led_count = 60 %}
        {##### CALCULATION #####}
        {# Fraction of the meter that should be on #}
        {% set sensor_value = states(sensor_entity_id) | float(default=0) %}
        {% set fraction = sensor_value / max_value %}
        {% set brightness = 50 %}
        {# LED parts: normal for the active bar, then highlight the exact value, then lowlight the inactive part #}
        {% set highlight_end_leds = (fraction * led_count) | int %}
        {% set normal_end_leds = highlight_end_leds - 3 %}
        {% set lowlight_start_leds = highlight_end_leds %}
        {# Resulting segments to send to WLED #}
        {# - Start with turning everything off #}
        {% set result = namespace(segments=[0, led_count, [0,0,0]]) %}
        {##### HELPERS #####}
        {# Add a segment, while a possible scaledown in brightness #}
        {# - Reversed start/end because strip is upside down #}
        {% macro segment(start_led, end_led, color, scaledown=1) -%}
          {%
            set result.segments = result.segments + [
              led_count - end_led,
              led_count - start_led,
              [
                (color[0] / scaledown) | round(0, 'ceil'),
                (color[1] / scaledown) | round(0, 'ceil'),
                (color[2] / scaledown) | round(0, 'ceil'),
              ]
            ]
          %}
        {%- endmacro %}
        {# List of color blocks, references sensor values #}
        {%
          set blocks = [
            {"start": 0, "end": 1000, "color": [255, 0, 0]},
            {"start": 1000, "end": 2000, "color": [255, 255, 0]},
            {"start": 2000, "end": max_value, "color": [0, 255, 0]},
          ]
        %}
        {% for block in blocks %}
          {# Convert sensor values of the block to led counts #}
          {% set block_start_leds = (block.start / max_value * led_count) | round %}
          {% set block_end_leds = (block.end / max_value * led_count) | round %}
          {# NORMAL segment #}
          {% if normal_end_leds >= block_start_leds %}
            {{
              segment(
                [block_start_leds, 0] | max,
                [block_end_leds, normal_end_leds] | min,
                block.color,
                scaledown = 6
              )
            }}
          {% endif %}
          {# HIGHLIGHT segment: If the highlight overlaps this block, add a highlight #}
          {% if ([block_start_leds, normal_end_leds] | max) <= ([block_end_leds, highlight_end_leds] | min) %}
          {{
            segment(
              [block_start_leds, normal_end_leds] | max,
              [block_end_leds, highlight_end_leds] | min,
              block.color,
              scaledown = 3
            )
          }}
          {% endif %}
          {# LOWLIGHT segment: when a part of this block is inactive #}
          {% if lowlight_start_leds <= block_end_leds %}
            {{
              segment(
                [block_start_leds, lowlight_start_leds] | max,
                [block_end_leds, led_count] | min,
                block.color,
                scaledown = 12
              )
            }}
          {% endif %}
        {% endfor %}
        {# Print results, passing it to the rest command #}
        {# - Use full segment brightness, but lower strip brightness #}
        {# - Turn on/off based on living room usage #}
        {{
          {
            "bri": brightness,
            "seg": {
              "bri": 255,
              "i": result.segments
            }
          }
        }}

type or paste code here

Anyone an idea?
At <1000W, it would be desirable for the first 5 LEDs to be red, then the next 5 orange <2000W, then all green.
Maximum power from the roof is 10000W.

Nobody an idear?

Hey @Camp3r! Welcome to the community & welcome to home assistant! It’s been a while since I last commented on this thread and I haven’t got the LED Strip running anymore - Infact I never installed it officially, I was just doing some testing.

Have you tried implementing the Automation instead of importing a blueprint? If so, can you post the automation you created?

If I insert this as sample in energy.yaml in the folder blueprints/automation/ then I got following error.

Invalid blueprint: required key not provided @ data[‘blueprint’]. Got None

I have no idea how to fix this…

Right, okay. The reason you are getting that error above is because it’s not a blueprint. It’s just a standard automation. So if you take the code and put it in your automations.y’all file then you shouldn’t see that error.

Sorry, if I put the code via the automation dashboard into the automation.ya…
than it look like that.

  • id: energy_usage_light_bar
    alias: Energy Usage LED
    description: Display a meter bar for the house energy usage
    trigger:
    • platform: state
      entity_id: sensor.total_dc_power
      action:
    • service: rest_command.wled_update
      data:
      payload: ‘{“on”: true}’
    • service: rest_command.wled_update
      data:
      payload: “{##### INPUTS #####} {# Use the value of the entity that has triggered
      the automation #} {% set sensor_entity_id = trigger.entity_id %} {% set sensor_entity_id
      = “sensor.total_dc_power” %} #{# Value that should fill the complete meter
      #} #{% set max_value = 100 %} {# Number of leds in the strip #} {% set led_count
      = 60 %} {##### CALCULATION #####} {# Fraction of the meter that should be
      on #} {% set sensor_value = states(sensor_entity_id) | float(default=0) %}
      {% set fraction = sensor_value / max_value %} {% set brightness = 50 %} {#
      LED parts: normal for the active bar, then highlight the exact value, then
      lowlight the inactive part #} {% set highlight_end_leds = (fraction * led_count)
      | int %} {% set normal_end_leds = highlight_end_leds - 3 %} {% set lowlight_start_leds
      = highlight_end_leds %} {# Resulting segments to send to WLED #} {# - Start
      with turning everything off #} {% set result = namespace(segments=[0, led_count,
      [0,0,0]]) %} {##### HELPERS #####} {# Add a segment, while a possible scaledown
      in brightness #} {# - Reversed start/end because strip is upside down #} {%
      macro segment(start_led, end_led, color, scaledown=1) -%}\n {%\n set result.segments
      = result.segments + [\n led_count - end_led,\n led_count - start_led,\n
      \ [\n (color[0] / scaledown) | round(0, ‘ceil’),\n (color[1]
      / scaledown) | round(0, ‘ceil’),\n (color[2] / scaledown) | round(0,
      ‘ceil’),\n ]\n ]\n %}\n{%- endmacro %} {# List of color blocks, references
      sensor values #} {%\n set blocks = [\n {“start”: 0, “end”: 1000, “color”:
      [255, 0, 0]},\n {“start”: 1000, “end”: 2000, “color”: [255, 255,
      0]},\n {“start”: 2000, “end”: max_value, “color”: [0, 255, 0]},\n
      \ ]\n%} {% for block in blocks %}\n {# Convert sensor values of the block
      to led counts #}\n {% set block_start_leds = (block.start / max_value * led_count)
      | round %}\n {% set block_end_leds = (block.end / max_value * led_count)
      | round %}\n {# NORMAL segment #}\n {% if normal_end_leds >= block_start_leds
      %}\n {{\n segment(\n [block_start_leds, 0] | max,\n [block_end_leds,
      normal_end_leds] | min,\n block.color,\n scaledown = 6\n )\n
      \ }}\n {% endif %}\n {# HIGHLIGHT segment: If the highlight overlaps this
      block, add a highlight #}\n {% if ([block_start_leds, normal_end_leds] |
      max) <= ([block_end_leds, highlight_end_leds] | min) %}\n {{\n segment(\n
      \ [block_start_leds, normal_end_leds] | max,\n [block_end_leds, highlight_end_leds]
      | min,\n block.color,\n scaledown = 3\n )\n }}\n {% endif %}\n
      \ {# LOWLIGHT segment: when a part of this block is inactive #}\n {% if lowlight_start_leds
      <= block_end_leds %}\n {{\n segment(\n [block_start_leds, lowlight_start_leds]
      | max,\n [block_end_leds, led_count] | min,\n block.color,\n
      \ scaledown = 12\n )\n }}\n {% endif %}\n{% endfor %} {# Print
      results, passing it to the rest command #} {# - Use full segment brightness,
      but lower strip brightness #} {# - Turn on/off based on living room usage
      #} {{\n {\n “bri”: brightness,\n “seg”: {\n “bri”: 255,\n
      \ “i”: result.segments\n }\n }\n}}”
      mode: single

What files I need that this worked, only the config.yaml and automation or one more in automations folder?

okay with this code I have now a result, but I don’t understand why only 3 leds of the stripe working and not a bar of the full stripe.

- id: 'energy_usage_light_bar' # To enable debug
  alias: Energy Usage LED
  description: Display a meter bar for the house energy usage
  mode: single
  trigger:
  # Update when the energy usage changes
  - platform: state
    entity_id: sensor.total_dc_power
  action:
  # - alias: "Turn on when living room in use, else turn off"
  #   # choose:
  #   sequence:
  - service: rest_command.wled_update
    data:
      payload: '{"on": true}'
  - service: rest_command.wled_update
    data:
      payload: >-
        {##### INPUTS #####}
        {# Use the value of the entity that has triggered the automation #}
        {% set sensor_entity_id = trigger.entity_id %}
        {% set sensor_entity_id = "sensor.total_dc_power" %}
        {# Value that should fill the complete meter #}
        {% set max_value = 10000 %}
        {# Number of leds in the strip #}
        {% set led_count = 60 %}
        {# List of color blocks, references sensor values #}
        {%
          set blocks = [
            {"start": 0, "end": 1000, "color": [0, 255, 0]},
            {"start": 1000, "end": 2000, "color": [255, 255, 0]},
            {"start": 2000, "end": max_value, "color": [255, 0, 0]},
          ]
        %}
        {##### CALCULATION #####}
        {# Fraction of the meter that should be on #}
        {% set sensor_value = states(sensor_entity_id) | float(default=0) %}
        {% set fraction = sensor_value / max_value %}
        {% set brightness = 100 %}
        {# LED parts: normal for the active bar, then highlight the exact value, then lowlight the inactive part #}
        {% set highlight_end_leds = (fraction * led_count) | int %}
        {% set normal_end_leds = highlight_end_leds - 3 %}
        {% set lowlight_start_leds = highlight_end_leds %}
        {# Resulting segments to send to WLED #}
        {# - Start with turning everything off #}
        {% set result = namespace(segments=[0, led_count, [0,0,0]]) %}
        {##### HELPERS #####}
        {# Add a segment, while a possible scaledown in brightness #}
        {# - Reversed start/end because strip is upside down #}
        {% macro segment(start_led, end_led, color, scaledown=1) -%}
          {%
            set result.segments = result.segments + [
              led_count - end_led,
              led_count - start_led,
              [
                (color[0] / scaledown) | round(0, 'ceil'),
                (color[1] / scaledown) | round(0, 'ceil'),
                (color[2] / scaledown) | round(0, 'ceil'),
              ]
            ]
          %}
        {%- endmacro %}

        {% for block in blocks %}
          {# Convert sensor values of the block to led counts #}
          {% set block_start_leds = (block.start / max_value * led_count) | round %}
          {% set block_end_leds = (block.end / max_value * led_count) | round %}
          {# NORMAL segment #}
          {% if normal_end_leds >= block_start_leds %}
            {{
              segment(
                [block_start_leds, 0] | max,
                [block_end_leds, normal_end_leds] | min,
                block.color,
                scaledown = 6
              )
            }}
          {% endif %}
          {# HIGHLIGHT segment: If the highlight overlaps this block, add a highlight #}
          {% if ([block_start_leds, normal_end_leds] | max) <= ([block_end_leds, highlight_end_leds] | min) %}
          {{
            segment(
              [block_start_leds, normal_end_leds] | max,
              [block_end_leds, highlight_end_leds] | min,
              block.color,
              scaledown = 3
            )
          }}
          {% endif %}
          {# LOWLIGHT segment: when a part of this block is inactive #}
          {% if lowlight_start_leds <= block_end_leds %}
            {{
              segment(
                [block_start_leds, lowlight_start_leds] | max,
                [block_end_leds, led_count] | min,
                block.color,
                scaledown = 12
              )
            }}
          {% endif %}
        {% endfor %}
        {# Print results, passing it to the rest command #}
        {# - Use full segment brightness, but lower strip brightness #}
        {# - Turn on/off based on living room usage #}
        {{
          {
            "bri": brightness,
            "seg": {
              "bri": 255,
              "i": result.segments
            }
          }
        }}

Make sure your WLED is set up correctly with the right number of LED’s & maybe try to set your max value very low and see if more LED’s light up.

Hi all,
found this topic way too late :slight_smile:

I also made a ledstrip-power meter, but took a different approach. Maybe this can be useful for someone so I will post my approach. When I started this project I used UDP in a python script to send packets to WLED for updates. This was NOT reliable at all.
Eventually I installed Esphome on my controller, essentially making the ledstrip a ‘lamp’ in HASS. Then I created an effect to display power usage, or delivery. The ESP32 gets the values through the native HASS API. This works flawlessly and has some big advantages over using WLED and sending packets and forgetting about them. You can use the strip as a light as well and have all the functions that normal lights have in HASS. If you want to show the power data you just activate the effect and you’re done.
I will post my code (YAML) for esphome. Please ignore the ‘timer’ parts, I’m experimenting with implementing cooking timers as well :slight_smile:

esphome:
  name: koffiecorner-ledstrip
  friendly_name: Koffiecorner LEDstrip

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  level: INFO

# Enable Home Assistant API
api:
  encryption:
    key: "[key]"

ota:


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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Koffiecorner LEDstrip AP"
    password: ""

captive_portal:

binary_sensor:
  - platform: status
    name: "Koffiecorner LEDstrip Status"

  - platform: homeassistant
    entity_id: light.aanrecht
    id: light_aanrecht

number:
  - platform: template
    name: "Speed factor"
    id: speed_factor
    entity_category: config
    min_value: 0.0
    max_value: 100.0
    initial_value: 50.0
    step: 1.0
    optimistic: true
    restore_value: true
    on_value:
      then:
       lambda: |-
        ESP_LOGI("custom", "Current speed_factor = %i", static_cast<int>(id(speed_factor).state));

switch:
  - platform: template
    name: "Background light with keuken"
    id: background_met_keuken
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON

  - platform: template
    name: Background always on
    id: background_always_on
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF

sensor:
  - platform: homeassistant
    entity_id: sensor.ledstrip_timer_remaining
    id: "timer_remaining"
    on_value:
      then:
        lambda: |-
          int old_current_led = id(timer_current_led);
          //id(timer_previous_led) = id(timer_current_led);
          id(timer_updated) = true; 
          id(minutes) = static_cast<int>(id(timer_remaining).state) / 60;
          id(seconds) = static_cast<int>(id(timer_remaining).state) - (id(minutes)*60);
          id(timer_current_led) = std::round((30.0/60.0) * static_cast<float>(id(seconds)));
          
          if (old_current_led != id(timer_current_led))
          {
            id(timer_previous_led) = old_current_led;
            id(led_updated) = true;
          }
          //ESP_LOGI("timer", "Time remaining: %i. Minutes: %i, seconds: %i, afkapled: %i", static_cast<int>(id(timer_remaining).state), id(minutes), id(seconds), id(timer_current_led));
          //ESP_LOGI("timer", "timer_current_led: %i, timer_previous_led: %i, old_current_led: %i", id(timer_current_led), id(timer_previous_led), old_current_led);

  - platform: homeassistant
    entity_id: input_number.ledstrip_scale
    id: "sensor_positive_scale"
    on_value: 
      then:
        lambda: |-
          id(positive_scale) = id(sensor_positive_scale).state;

  - platform: homeassistant
    entity_id: input_number.ledstrip_scale_delivery
    id: "sensor_negative_scale"
    on_value: 
      then:
        lambda: |-
          id(negative_scale) = id(sensor_negative_scale).state;

  - platform: homeassistant
    id: "sensor_current_netto"
    entity_id: sensor.current_usage_delivery_netto_w
    on_value:
      then:
        lambda: |-
          id(start_led) = id(target_led);
          id(current_netto) = static_cast<double>(id(sensor_current_netto).state);
          
          if (id(current_netto) < 0.0)
          {
            id(target_led) = static_cast<int>(id(current_netto)/id(negative_scale)*32);
          }
          else if (id(current_netto) > 0.0)
          {
            id(target_led) = static_cast<int>(id(current_netto)/id(positive_scale)*32);
          }
          else
          {
            id(target_led) = 0;
          }
          //ESP_LOGD("custom", "Current_netto is: %f", id(current_netto));
          //ESP_LOGD("custom", "This means target led is: %i and start_led is: %i", id(target_led), id(start_led));
          if (id(target_led) != id(active_led))
          {
            id(movement_delay) = static_cast<int>(id(speed_factor).state/(abs(id(target_led) - id(active_led))));
            //ESP_LOGD("custom", "Movement delay = %i", id(movement_delay));
          }

  
globals:
  - id: positive_scale
    type: double

  - id: negative_scale
    type: double

  - id: current_netto
    type: double

  - id: active_led
    type: int
  
  - id: start_led
    type: int

  - id: target_led
    type: int

  - id: movement_delay
    type: int

  - id: minutes
    type: int

  - id: seconds
    type: int

  - id: timer_current_led
    type: int
  
  - id: timer_previous_led
    type: int

  - id: timer_updated
    type: boolean

  - id: led_updated
    type: boolean

light:
  - platform: neopixelbus
    variant: WS2812X
    pin: GPIO2
    num_leds: 32
    type: GRB
    name: "Koffiecorner LEDstrip"
    id: Koffiecorner_ledstrip
    effects: 
      - addressable_lambda: 
          name: "Show current power data"
          update_interval: 1ms
          lambda: |-

            // Define the colors to use
            static Color color;
            static Color red = Color(255,0,0);
            static Color orange = Color(255,85,0);
            static Color green = Color(0,255,0);
            static Color pink = Color(255,0,190);
            static Color blue = Color(0,0,255);
            static Color black = Color(0,0,0);
            static Color rest = black;
            static Color background = Color (255,175,95);
            

          

            // Declare some variables
            static int direction = 1;
            static int gapsize = 1;
            static int current_wait_time = 0;
            static int current_led = 0;
 

            // Create the gradient with three colors, evenly distributed over the amount of LED's
            // Fill in what colors you'd like
            static Color color1 = green;
            static Color color2 = orange;
            static Color color3 = red; 

            // Determine the middle of the strip
            static int breakpoint = it.size()/2; 

            // Determine the amount of change per rgbvalue per LED
            static int stepR1 = (color1[0]-color2[0])/breakpoint;
            static int stepG1 = (color1[1]-color2[1])/breakpoint;
            static int stepB1 = (color1[2]-color2[2])/breakpoint;
            static int stepR2 = (color2[0]-color3[0])/breakpoint;
            static int stepG2 = (color2[1]-color3[1])/breakpoint;
            static int stepB2 = (color2[2]-color3[2])/breakpoint;
            
            if (initial_run) 
            {
              ESP_LOGI("custom", "initial_run performed");
            }

            if ((id(light_aanrecht).state && id(background_met_keuken).state) || id(background_always_on).state) 
            {
              rest = background;
            }
            else
            {
              rest = black;
            }

            if (id(target_led) < id(active_led))
            { 
              direction = -1; 
            }
            else 
            {
              direction = 1; 
            }
            
            current_led = id(active_led);

            if (current_led >= 0)
            {
              for (int i = 0; i < it.size() ; i++) 
              {
                if (i<current_led)
                {
                  if (i<=breakpoint)
                  {
                    color = Color(color1[0]-(stepR1*(i+1)),color1[1]-(stepG1*(i+1)),color1[2]-(stepB1*(i+1)));
                  }
                  else 
                  {
                    color = Color(color2[0]-(stepR2*(i-breakpoint)),color2[1]-(stepG2*(i-breakpoint)),color2[2]-(stepB2*(i-breakpoint)));
                  }
                  if (i==(current_led-1))
                  {
                    it[i] = color.fade_to_black(150);
                  }
                  else
                  {
                    it[i] = color;
                  }
                }
                else
                {
                  if (i < current_led + gapsize)
                  {
                    it[i] = black;
                  }
                  else
                  {
                    if (i==(current_led+gapsize))
                    {
                      it[i] = rest.fade_to_black(150);
                    }
                    else
                    {
                      it[i] = rest;
                    }
                  }
                }
              }
            }
            else
            {
              for (int i = it.size()-1; i>=0; i--)
              {
                if (i>=it.size()+current_led)
                {
                  if (i==it.size()+current_led)
                  {
                    it[i] = pink.fade_to_black(150);
                  }
                  else
                  {
                    it[i] = pink;
                  }
                }
                else
                {
                  if (i>it.size()-1+current_led-gapsize)
                  {
                    it[i] = black;
                  }
                  else
                  {
                    if (i==it.size()-1+current_led-gapsize)
                    {
                      it[i] = rest.fade_to_black(150);
                    }
                    else
                    {
                      it[i] = rest;
                    }

                  }
                }
              }
            }
            
            if (id(active_led) != id(target_led))
            {
              if (current_wait_time < id(movement_delay))
              {
                ++current_wait_time;
              }
              else
              {
                //ESP_LOGD("custom", "I need to move! old= %i new= %i", id(active_led), id(target_led));
                //ESP_LOGD("custom", "Current delay = %i ms", id(movement_delay));
                id(active_led) = id(active_led) + direction;
                current_wait_time = 0;
              }
            }
            else
            {
              //ESP_LOGD("custom", "I won't move! old= %i new= %i", id(active_led), id(target_led));
            }
            


Maybe someone will be happy to try this as well, so feel free to use and alter my code!
I also use a couple of helpers so you can dynamically change the scale and speed of the meter from the UI in HASS. You have to create these of course.

1 Like

Hello everyone,
Found that post looking for something else, but figuring out I can contribute.
I have installed solar panels and wanted to monitor power consumption and surplus easily with light strips. The way it looks is I have 2 led strips side by side, one showing the solar production (blue), one for consumption (orange) , when length of production is higher than consumption, you know you have surplus. I use a individually addressable LED bar, split in 2 segments using WLED, I’m using the effect « percentage » to display the percentage of power.

Here is a pic, it’s early morning so I have almost no prod (blue), and cooking so I have a lot of consumption (orange).

1 Like

A little later in the day, with consumption matching production thanks to a solar router linked to ly electric water boiler.

That looks great. Would you be so kind and share your code/setup? I’m fairly new to this but I was looking to do the same thing.

I’m also wondering how to use the percentage effect to display

Hello Thijs, many thanks for all your inputs and the code you provided.
Inspired by Hands on Katie on youtube, ( https://www.youtube.com/watch?v=agsibVa3mNs&t=606s ) and the code from the “elpris” before in this thread, I started to dig into your code. Being new to yaml, it took me many days to get to something useful.
Now I changed the code to show different sensors on one 9 ring 241 led ring. - for each value I set up a different automation, each with an offset to engage the next ring. (code follows)

I still have a few things I do not understand and hope you can help me. Definitely caused by not understanding exactly how the segment array is set up. I spent a lot of time breaking down the code in the template tools to see what happens in the for loop. What also took me ages is to find out how to get the offset to work properly. For some reason, I had to add the offset to the highlight setup, and deduct it later in the segment built up.

  1. where does the first setup of the leds happen? I notice that the non-highlighted ones display 3 colors - and the values show up in 4 colors as determined by the color blocks. I tried to add a line to the color “(color[0] / scaledown) | round(0, ‘ceil’),” lines, but that seemed to mess everything up. Not sure what the color[0] refers to, or whether it is a placeholder to be filled later in the set blocks part.

  2. The display works counter clockwise. I tried to figure out how to make it do clockwise. Changing the WLED settings to “reversed” definitely does not work. At the beginning of the helpers section your mention:
    {##### HELPERS #####}
    {# Add a segment, while a possible scaledown in brightness #}
    {# - Reversed start/end because strip is upside down #}
    I really do not understand how that works - but it must be in this section.

  3. Is there any way to apply an effect to a segment of the leds (e.g. blinking) ? I tried to find how to do that with adding it to the payload, however of no avail. The WLED API documentation is not clear about it for me. I tried variations of the following, however no happy outcome yet:
    {{
    {
    “bri”: brightness,
    “seg”: {
    “bri”: 255,
    “fx”: 2,
    “i”: result.segments
    }
    }
    }}

So the rings show different values:
Outer to inner:

  1. Current power use. Full ring max set to 5000 - which is what we kind of use at one time - ironing, cooking and washing will go over that…
  2. Solar Battery level. We keep 20% unused - in case power goes down we still can run some essentials. So 20% is red. Max is set to 100%
  3. Water use (cumulative) during the day - in blue tones. Set to Max of 500 l per day
  4. Gas use (cumulative) during the day. Does not (yet) work - I cannot seem to get the utility sensor setup so it shows the cumulative amount as in the energy dashboard. Work in progress, tinkering with settings.
  5. Center piece - Solar production - nothing in the picture - murky weather when I took the picture. Max set to 10.000.

obviously the code will work on a straight line led. Next step for me is adding a ring for other values.

Bij voorbaat dank voor je feedback!

Code: (off inner part)
max value - maximum value that will fill the number of leds; I used this as parameter in the set blocks section, so I do not have to change it for each automation.
led_count - number of leds
led_offset - where to start

# ----------------------------------------------------------------------#
- id: 'elpris_led_bar_5_solar'
  alias: Elpris led bar 5 - solar
  description: ''
  triggers:
  - trigger: state
    entity_id:
    - sensor.rct_power_storage_all_generators_power
  actions:
    - action: rest_command.wled_update
      data:
        payload: '{\"on\": true}'
    #- action: rest_command.wled_update
    #  data:
    #    payload: '{"FX": 1}'
    - action: rest_command.wled_update
      data:
        payload: >-
          {##### INPUTS #####}
          {# Use the value of the entity that has triggered the automation #}
          {% set sensor_entity_id = trigger.entity_id %}  
          {# Value that should fill the complete meter #}   
          {% set max_value = 10000 %}  
          {# Number of leds in the strip #}  
          {% set led_count = 61 %}  
          {% set led_offset = 180 %}  
          {##### CALCULATION #####}  
          {# Fraction of the meter that should be on - note - changed to test . should be  #}   
          {% set sensor_value = ((states.sensor.rct_power_storage_all_generators_power.state | float|int)) %}
           {% set sensor_value = sensor_value+100 %}   
          {% set fraction = sensor_value / max_value | int | round(0) %}  
          {% set brightness = 70 %}  
          {# LED parts:normal for the active bar, then highlight the exact value, then
          lowlight the inactive part #}   
          {% set highlight_end_leds = led_offset+(fraction * led_count) | int  %} 
          {% set normal_end_leds =  highlight_end_leds - 3%}
          {% set lowlight_start_leds = highlight_end_leds %}  
          {# Resulting segments to send to WLED #}  
          {# - Start with turning everything off #} 
          {# no 1 #}
          {% set result = namespace(segments=[0+led_offset, led_count+led_offset, [0,0,0]]) %}  
          {##### HELPERS #####} 
          {# Add a segment, while a possible scaledown in brightness #}  
          {# - Reversed start/end because strip is upside down #} 
          {% macro segment(start_led, end_led, color, scaledown=3) -%}
          {# no 2 ? 3, 4?#}
            {% set result.segments = result.segments + [(led_count - end_led)+led_offset,
                (led_count - start_led) + led_offset,
                [
                  (color[0] / scaledown) | round(0, 'ceil'),
                  (color[1] / scaledown) | round(0, 'ceil'),
                  (color[2] / scaledown) | round(0, 'ceil'),
                ]
              ]
            %}
          {%- endmacro %} {# List of color blocks, references sensor values #} 
          {% set blocks = [
              {"start": 0, "end": (max_value/4), "color": [255, 0, 0]},
              {"start": (max_value/4), "end": ((max_value/4)*2), "color": [255, 126, 165]},
              {"start": ((max_value/4)*2), "end": ((max_value/4)*3), "color": [255, 255, 0]},
              {"start": ((max_value/4)*3), "end": max_value, "color": [0, 255, 0]},] %}
            {% for block in blocks %}
            {# Convert sensor values of the block to led counts #}
             {% set block_start_leds = led_offset+(block.start / max_value * led_count) | round %}
              {% set block_end_leds = led_offset+(block.end / max_value * led_count) | round %}
            {# NORMAL segment no 5?? #}
              {% if normal_end_leds >= block_start_leds %}
                {{
                  segment(
                    [block_start_leds-led_offset, 0-led_offset] | max | int | round(0),
                    [block_end_leds-led_offset, normal_end_leds-led_offset] | min | int | round(0),
                    block.color,
                    scaledown = 1.4
                  )
                }}
              {% endif %}
            {# HIGHLIGHT segment: No 6? If the highlight overlaps this block, add a highlight #}
              {% if ([block_start_leds, normal_end_leds] | max) <= ([block_end_leds, highlight_end_leds] | min) %}
              {{
                segment(
                  [block_start_leds-led_offset, normal_end_leds-led_offset] | max| int | round(0),
                  [block_end_leds-led_offset, highlight_end_leds-led_offset] | min| int | round(0),
                  block.color,
                  scaledown = 1.4
                )
              }}
              {% endif %}
            {# LOWLIGHT segment: No 7? when a part of this block is inactive #}
              {% if lowlight_start_leds <= block_end_leds %}
                {{
                  segment(
                    [block_start_leds-led_offset, lowlight_start_leds-led_offset] | max| int | round(0),
                    [block_end_leds-led_offset, led_count] | min| int | round(0),
                    block.color,
                    scaledown = 3.7
                  )
                }}
              {% endif %}
            {% endfor %} {# Print results, passing it to the rest command #} {# -
          Use full segment brightness, but lower strip brightness #} {# - Turn
          on/off based on living room usage #} 
          {{
            {
              "bri": brightness,
              "seg": {
                "bri": 255,
                "fx": 2,
                "i": result.segments
              }
            }
          }}
  mode: single
# ----------------------------------------------------------------------#

Nice that you like this idea, and that my code got you started. I’ll stick to English here so that others can follow along. I’ll try to answer your questions about it.

The template is complicated because it separates the mapping of sensor value ranges to colors, from the mapping to the physical led strip ranges.
Couple of definitions I used:

  • blocks: a list where each entry defines a mapping from a sensor value range to a certain color (e.g. 0-1000 CO2 to green, 1000-1500 CO2 to orange, 1500-2000 to red)
  • segment: range of physical leds to turn to a certain color
    • This is the end result the WELD api accepts
    • These are collected in the result.segments variable name of the template
  • macro segment(…): function that can be called to add a segment to the result
    • It does some math using the ‘scaledown’ value to be able to dim all segments relative to each other, because I want to make the whole bar less bright during the evening (it does this by lowering the red/green/blue values, basically mixing it with ‘black’).

To do the mapping of ‘blocks’ to ‘segments’ the template loops through every defined ‘block’ (using the ‘for block in blocks’ line). For every one of those blocks it checks which pieces need to light up in which way, choosing between:

  • normal: active part of the bar, medium brightness
  • highlight: brighter part that indicates the current value
  • lowlight: inactive part of the bar, just to show which colors come when the value would be higher

For example, for a value of 1200 CO2, my blocks would do this:

  • First block: will only show as ‘normal’, because the value is higher than 1000 (end of block)
  • Second block:
    • will have 1000-1200 calculated as ‘normal’
    • will have 3 LEDS around the 1200 value as ‘highlight’
    • will have 1200-1500 as ‘lowlight’
  • Third block: will only show ‘lowlight’, because the sensor value is lower than the start of the block

Your questions:

  1. Each ‘block’ in the blocks list defines a range of sensor values that you want to represent with a certain color. Basically if a block covers half your sensor value range (in your case 0-5000 would cover half of the max_value of 1000), it will use the specified color for half the LEDS on the strip. This is a way to decouple the sensor value ranges from the LED count ranges, so that you can update either one of them easily (and the template calculates the mapping). You might want to update to my later version, which determines the min/max sensor value from that blocks list automatically.
    • The “(color[0] / scaledown) | round(0, ‘ceil’)” lines are part of the segment macro, and are handling the red/green/blue color values, so you never have to add more of those lines, you only have to add more entries to the ‘blocks’ list if you want to have more color pieces.
  2. For some reason WLED does not apply the reverse setting to this api, I also ran into this issue. To remove my flipping code, update the macro to the block below (only the start_led/end_led lines changed).
                  {% macro segment(start_led, end_led, color, scaledown=1) -%}
                    {%
                      set result.segments = result.segments + [
                        start_led,
                        end_led,
                        [
                          (color[0] / scaledown) | round(0, 'ceil'),
                          (color[1] / scaledown) | round(0, 'ceil'),
                          (color[2] / scaledown) | round(0, 'ceil'),
                        ]
                      ]
                    %}
                  {%- endmacro %}
    
  3. Effects don’t work with this way of setting the LEDs as far as I know, that might have changed in newer WLED versions though, I have not checked that. When I built this display the api I’m using here was brand new in WLED. What might be possible is simply updating the brightness through the regular WLED integration, that should keep the color blocks are applied through this script.

Let me know if that helps to clarify a few things!

1 Like

Thank you, Thys, appreciate your explanations, and see now what adding blocks would do. Great thinking on your part to make it hanging together. I am still a baby in understanding parts of the yaml language, how it calls a macro and all - your code has made me study it. I’ll try your suggestion for the reverse setting.

As to the effects in WLED, I wonder whether we could write code which creates LED segments - which could be given an effect.

In the meantime I added a LED showing two outputs from Apollo CO2 sensors, and experimented with turning the LEDs of when the radar sensor notices when we are not in the room like in your code - it works - now have to add it to all 7 pieces of code.

Which made me think - I have now 7 separate versions of this code for each part of the LED. It should be possible to have one version of the code and loop through it with the set of each of the parts we want to display. Having said that - each of the parts have sensors that update at different times. Now, the particular parts (gas, water, etc) update when they get new values - in the suggested “one code” version it would update the LED on each value change. Each loop could create a WLED segment with its own effect. (when you look at the code of the effects in WLED, they seem to be looping macros) Just a thought.
Thanks again!

As to the macro, in my case they currently looks like this:

% macro segment(start_led, end_led, color, scaledown=3) -%}
          {# no 2 ? 3, 4?#}
            {% set result.segments = result.segments + [(led_count - end_led)+led_offset,
                (led_count - start_led) + led_offset,
                [
                  (color[0] / scaledown) | round(0, 'ceil'),
                  (color[1] / scaledown) | round(0, 'ceil'),
                  (color[2] / scaledown) | round(0, 'ceil'),
                ]
              ]
            %}
          {%- endmacro %} {# List of color blocks, references sensor values #} 

Let’s try to change your new macro to fit my setup…

{% macro segment(start_led, end_led, color, scaledown=3) -%}
          {# no 2 ? 3, 4?#}
            {% set result.segments = result.segments + [start_led + led_offset,
                (end_led) + led_offset,
                [
                  (color[0] / scaledown) | round(0, 'ceil'),
                  (color[1] / scaledown) | round(0, 'ceil'),
                  (color[2] / scaledown) | round(0, 'ceil'),
                ]
              ]
            %}
          {%- endmacro %} {# List of color blocks, references sensor values #} 

Works like a dream! They all go clockwise now! Thanks Thys!