LED Streifen (WS2812) elegant einzelne LEDs ansteuern?

Hallo zusammen,

ich habs inzwischen geschafft, meine 10 LEDs einzeln mit ESPHome anzusteuern. Jetzt suche ich eine elegante Automation, diese in Abhängigkeit des Ladezustands meiner Solarbatterie anzusteuern. Mit einzelnen Triggern (> 10, >20…) und das Abschalten dann auch mit einzelnen Triggern geht das, aber ist diese Monster Automation sinnvoll/elegant. Hat jemand eine bessere Idee?

Gruß RUDI

Keine Automation! Lass das doch den ESP mitmachen. Du kannst ihm doch einfach dazu bringen, dass er den SOC selber ausliest und entsprechend die LEDs nach einer Logik ansteuert. Dafür müsste ich sogar noch ein fertiges Programm da liegen haben. Ich kann dir das gern mal raussuchen und zur Verfügung stellen. Musst dich aber bis morgen gedulden, da ich heut erst spät nach Hause komme.

danke, ich schaue es mir morgen dann mal an.

Gib doch Deine bestehende YAML-Konfiguration für ESPHome der KI und teile ihr Deine Wünsche mit, dann bekommst Du eine YAML, die Du probieren kannst.
Ich habe mir mal per KI einen Tester für die WS8212 programmieren lassen, die KI kann das.

hier mal ein link zu meiner wled ladezustand anzeige , die Visualisierung im gemini chat zeigt der sehr schön wie das licht auf Akku Ladung und ladezustände laden/entladen reagiert

Die Kernlogik deines WLED-Controllers

1. Die Variablen (Das Fundament)

  • Toleranzbereich (power): Wenn die Akkuleistung zwischen -15 W und +15 W schwankt, wird sie auf 0 gesetzt. Das verhindert, dass die Ladeanzeige bei minimalen Standby-Strömen nervös hin und her springt.
  • Segment-Berechnung (active_leds): Die 20 Segmente deines Streifens werden erst ab einem Akkustand von 11 % aktiviert. 11 % = 0 LEDs, 100 % = 20 LEDs.
  • Farbverlauf (rgb_r, rgb_g, rgb_b): Die Farbe berechnet sich fließend anhand des Akkustands. Sie startet bei Rot (leer), geht über Gelb und endet bei reinem Grün (voll). Blau ist permanent deaktiviert (0).

2. Die 5 Verhaltenszustände (Die Trigger)

  • Tiefenentladen (Akku $\le$ 10 %): Der WLED-Streifen wird komplett ausgeschaltet (on_state: "false").
  • Laden (Akku 11 - 99 % & Leistung > 0): Der Streifen schaltet sich ein. Effekt 28 (vermutlich ein Lauflicht/Chase-Effekt) wird vorwärts abgespielt (reverse: "false"). Die Geschwindigkeit skaliert dynamisch mit der Ladeleistung (bis max. 800 W).
  • Entladen (Akku > 10 % & Leistung < 0): Gleiches Verhalten wie beim Laden, aber das Lauflicht läuft rückwärts (reverse: "true").
  • Voll & Laden (Akku $\ge$ 100 % & Leistung > 0): Alle 20 LEDs leuchten in reinem Grün. Effekt 2 wird aktiviert (vermutlich ein Pulsieren/Breathe-Effekt) bei konstanter Geschwindigkeit.
  • Leerlauf/Standby (Akku > 10 % & Leistung = 0 W): Die aktuell berechnete Anzahl an LEDs leuchtet in der entsprechenden Farbe. Effekt 2 (Pulsieren) läuft sehr langsam (speed: 15), um Betriebsbereitschaft ohne Energiefluss zu signalisieren.

Um das Zusammenspiel dieser Werte greifbar zu machen, habe ich einen interaktiven Simulator für deinen LED-Streifen generiert. Du kannst mit dem Akkustand und der Leistung experimentieren, um das Verhalten deiner Automatisierung in Echtzeit zu testen:

alias: Balkonkraftwerk WLED API Ladeanzeige
description: Lebendige Ladeanzeige mit Toleranzbereich und Zweifarb-Logik.
triggers:
  - entity_id:
      - sensor.solarbank_3_e2700_pro_ladestand
      - sensor.solarbank_3_e2700_pro_akkuleistung
    trigger: state
conditions: []
actions:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ battery <= 10 }}"
        sequence:
          - data:
              on_state: "false"
              active_leds: 0
              fx: 0
              speed: 0
              r1: 0
              g1: 0
              b1: 0
              r2: 0
              g2: 0
              b2: 0
              reverse: "false"
            action: rest_command.wled_balkon_update
      - conditions:
          - condition: template
            value_template: "{{ battery > 10 and battery < 100 and power > 0 }}"
        sequence:
          - data:
              on_state: "true"
              active_leds: "{{ active_leds }}"
              fx: 28
              speed: "{{ [20, [((power / max_power) * 255) | int, 255] | min] | max }}"
              r1: 255
              g1: 255
              b1: 255
              r2: "{{ rgb_r }}"
              g2: "{{ rgb_g }}"
              b2: "{{ rgb_b }}"
              reverse: "false"
            action: rest_command.wled_balkon_update
      - conditions:
          - condition: template
            value_template: "{{ battery > 10 and power < 0 }}"
        sequence:
          - data:
              on_state: "true"
              active_leds: "{{ active_leds }}"
              fx: 28
              speed: >-
                {{ [20, [((power | abs / max_power) * 255) | int, 255] | min] |
                max }}
              r1: 255
              g1: 255
              b1: 255
              r2: "{{ rgb_r }}"
              g2: "{{ rgb_g }}"
              b2: "{{ rgb_b }}"
              reverse: "true"
            action: rest_command.wled_balkon_update
      - conditions:
          - condition: template
            value_template: "{{ battery >= 100 and power > 0 }}"
        sequence:
          - data:
              on_state: "true"
              active_leds: 20
              fx: 2
              speed: 128
              r1: 0
              g1: 255
              b1: 0
              r2: 0
              g2: 0
              b2: 0
              reverse: "false"
            action: rest_command.wled_balkon_update
      - conditions:
          - condition: template
            value_template: "{{ battery > 10 and power == 0 }}"
        sequence:
          - data:
              on_state: "true"
              active_leds: "{{ active_leds }}"
              fx: 2
              speed: 15
              r1: "{{ rgb_r }}"
              g1: "{{ rgb_g }}"
              b1: "{{ rgb_b }}"
              r2: 0
              g2: 0
              b2: 0
              reverse: "false"
            action: rest_command.wled_balkon_update
mode: restart
variables:
  max_power: 800
  battery: "{{ states('sensor.solarbank_3_e2700_pro_ladestand') | float(0) }}"
  raw_power: "{{ states('sensor.solarbank_3_e2700_pro_akkuleistung') | float(0) }}"
  power: |
    {% if raw_power > -15 and raw_power < 15 %}
      0
    {% else %}
      {{ raw_power }}
    {% endif %}
  active_leds: >
    {% set pct = (battery - 11) / 89 %} {% set pct = [0, [pct, 1] | min] | max
    %} {{ (pct * 20) | round(0) | int }}
  rgb_r: >
    {% set pct = (battery - 11) / 89 %} {% set pct = [0, [pct, 1] | min] | max
    %} {{ 255 if pct < 0.5 else (255 - ((pct - 0.5) * 2 * 255)) | int }}
  rgb_g: >
    {% set pct = (battery - 11) / 89 %} {% set pct = [0, [pct, 1] | min] | max
    %} {{ (pct * 2 * 255) | int if pct < 0.5 else 255 }}
  rgb_b: 0

vielleicht inspiriert dich meine Umsetzung - - ich bin mit ergebnis sehr zufrieden




ezgif-6ec39d89ae856ee3

So hier nun der versprochene Code!
Es war eigentlich mal als „Füllstandsanzeige für eine Zisterne“ gemacht!
Aber macht auch als „SOC Anzeige“ einen guten Job!

Ich habe es nochmal überarbeiten lassen und jetzt schaut es so aus:

ESPHome – SOC LED-Balken (10 × WS2812B)

soc_led_bar

oder so: (12 LED Ring, habe ich bei der Zisterne verwendet!)

soc_led_ring

0 – 9 % LED 0 (pulsierend rot) | Warnung
10 – 19 % LED 0 | Rot
20 – 29 % LED 0 – 1 | Rot → Gelb
50 – 59 % LED 0 – 4 | Gelb
80 – 89 % LED 0 – 7 | Gelb → Grün
100 % LED 0 – 9 | Grün

# =============================================================================
# ESPHome – SOC LED-Balken (10 × WS2812B)
#
# Hardware:
#   - Standard: ESP32 DevKit (38-Pin, ESP32-D0WDQ6), Datenleitung GPIO5
#   - Alternativ: ESP32-C3 SuperMini, Datenleitung GPIO4
#                 (siehe auskommentierte Blöcke in "substitutions" und "esp32")
#
# LED-Plattform: esp32_rmt_led_strip (empfohlen für ESP32 + ESP-IDF)
#   - Es wird AUSSCHLIESSLICH die offizielle light.addressable_set-Action genutzt.
#
# ESPHome >= 2026.1, Framework: ESP-IDF
# =============================================================================

substitutions:
  device_name:    "esp-led-soc"
  friendly_name:  "ESP SOC LED Bar"
  soc_entity_id:  "input_number.pseudo_soc"   # ← einzige nötige Anpassung
  led_data_pin:   "GPIO5"                   # ESP32 DevKit
  # ---------------------------------------------------------------------------
  # Alternative: ESP32-C3 SuperMini
  #   1) led_data_pin auf GPIO4 ändern
  #   2) In "esp32:" board auf "esp-c3-devkitm-1" umstellen
  # ---------------------------------------------------------------------------
  # led_data_pin: "GPIO4"
  # ---------------------------------------------------------------------------
  led_count:      "10" # Anzahl LEDs im Streifen
  update_interval: "60s"

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}
  min_version: 2026.1.0
  on_boot:
    priority: -100
    then:
      - light.turn_off: soc_led_bar
      - script.execute: update_leds

esp32:
  board: esp32dev
  framework:
    type: esp-idf
  # ---------------------------------------------------------------------------
  # Alternative: ESP32-C3 SuperMini
  # ---------------------------------------------------------------------------
  # board: esp-c3-devkitm-1
  # framework:
  #   type: esp-idf
  # ---------------------------------------------------------------------------

logger:
  level: INFO

api:
  encryption:
    key: !secret api_encryption_key # hier API Key definieren
  reboot_timeout: 15min


ota:
  - platform: esphome
    password: !secret ota_password # hier OTA Passwort definieren

wifi:
  ssid: !secret wifi_ssid # hier WLAN-SSID definieren
  password: !secret wifi_password # hier WLAN-Passwort definieren
  ap:
    password: !secret ap_password # hier AP-Passwort definieren

captive_portal:

# -----------------------------------------------------------------------------
# Globale Variablen für SOC-Wert, aktive LEDs, interpolierte Farbe und Helligkeit.
# -----------------------------------------------------------------------------
globals:
  - id: soc_value
    type: float
    initial_value: "0.0"
    restore_value: false
  - id: calc_active
    type: int
    initial_value: "0"
    restore_value: false
  - id: calc_r
    type: float
    initial_value: "0.0"
    restore_value: false
  - id: calc_g
    type: float
    initial_value: "0.0"
    restore_value: false
  - id: calc_b
    type: float
    initial_value: "0.0"
    restore_value: false
  - id: calc_brightness
    type: float
    initial_value: "0.5"
    restore_value: false

# -----------------------------------------------------------------------------
# Helligkeits-Slider (erscheint automatisch als number-Entität in HA)
# -----------------------------------------------------------------------------
number:
  - platform: template
    name: "Helligkeit"
    id: led_helligkeit
    min_value: 1
    max_value: 100
    step: 1
    initial_value: 50
    restore_value: false
    unit_of_measurement: "%"
    icon: "mdi:brightness-6"
    mode: slider
    # set_action (NICHT on_value) ist entscheidend:
    #   - on_value feuert AUCH beim internen publish_state(50) waehrend setup()
    #     -> das ist die Boot-Race-Condition, die den LED-Buffer crasht
    #     ("StoreProhibited" in ESPRangeView::set_red) und einen OTA-Rollback
    #     auf die alte Firmware ausloest.
    #   - set_action feuert NUR, wenn HA den Slider bewegt -> genau das, was
    #     wir wollen.
    optimistic: true
    set_action:
      - script.execute: update_leds

# -----------------------------------------------------------------------------
# SOC-Sensor von Home Assistant
# -----------------------------------------------------------------------------
sensor:
  - platform: homeassistant
    id: soc_sensor
    entity_id: ${soc_entity_id}
    on_value:
      then:
        - lambda: |-
            id(soc_value) = x;
            if (isnan(x)) {
              id(soc_text).publish_state("--");
            } else {
              char buf[16];
              snprintf(buf, sizeof(buf), "%.0f %%", x);
              id(soc_text).publish_state(buf);
            }
        - script.execute: update_leds

# -----------------------------------------------------------------------------
# Textuelle Anzeige des SOC, z. B. "73 %"
# -----------------------------------------------------------------------------
text_sensor:
  - platform: template
    id: soc_text
    name: "Batterie SOC"
    icon: "mdi:battery"
    update_interval: never

# -----------------------------------------------------------------------------
# Sicherheits-Intervall: alle 60 s LEDs neu berechnen,
#   falls HA keinen neuen Wert sendet.
# Blink-Intervall (Low-Battery-Warnung): toggelt LED 0 alle 500 ms,
#   solange SOC < 10 %. Helligkeit folgt dem Slider (calc_brightness).
# -----------------------------------------------------------------------------
interval:
  - interval: ${update_interval}
    then:
      - script.execute: update_leds
  - interval: 100ms
    then:
      - if:
          condition:
            lambda: 'return id(soc_value) < 10.0f;'
          then:
            # LED 0 dauerhaft rot, Helligkeit pulsiert sinusfoermig
            # ("Atmen") zwischen 10 % und 100 % des Slider-Werts.
            # Periode: 2000 ms (1 s hell, 1 s dunkel), keine harte An/Aus-
            # Kante -> kein Flackern, kein "global off"-Bug.
            - light.addressable_set:
                id: soc_led_bar
                range_from: 0
                range_to:   0
                red:        1.0
                green:      0.0
                blue:       0.0
                # t in [0,1)  ->  sin(2*pi*t) in [-1,1]
                # -> 0.5+0.5*sin(...) in [0,1]  (sanfter 0%-100%-Verlauf)
                # -> (0.1 + 0.9 * ...) in [0.1, 1.0]  (geclamped, lebt immer)
                # -> * calc_brightness  (Slider folgt)
                color_brightness: !lambda |-
                    float t = (millis() % 2000) / 2000.0f;
                    float pulse = 0.5f + 0.5f * sin(6.2831853f * t);
                    return id(calc_brightness) * (0.1f + 0.9f * pulse);

# -----------------------------------------------------------------------------
# Script: berechnet aktive LEDs + interpolierte Farbe und schickt alles
#   an den LED-Streifen (ausschließlich über light.addressable_set).
# -----------------------------------------------------------------------------
script:
  - id: update_leds
    # mode: queued statt restart
    #   - restart: cancelt den laufenden Run sofort, was bei schnellen
    #     SOC-Aenderungen zu abgeschnittenen addressable_set-Calls und
    #     damit zu Fehlfarben fuehren kann.
    #   - queued: jeder Run wird sauber zu Ende gefuehrt, bevor der
    #     naechste startet. Verhindert Mischfarben durch halbfertige
    #     RMT-Frames und sorgt fuer konsistente LED-Zustaende.
    mode: queued
    max_runs: 5
    then:
      - lambda: |-
          // 1) SOC clamping
          float soc = id(soc_value);
          if (isnan(soc))  soc =   0.0f;
          if (soc <   0.0f) soc =   0.0f;
          if (soc > 100.0f) soc = 100.0f;

          // 2) Anzahl aktiver LEDs (10 % pro LED)
          int active = (int)(soc / 10.0f);
          if (active > 10) active = 10;
          if (active <  0) active =  0;

          // 3) Farbinterpolation (linear):
          //      0 %  → Rot   (255,   0,   0)
          //     50 %  → Gelb  (255, 180,   0)
          //    100 %  → Grün  (  0, 255,   0)
          float r = 0.0f, g = 0.0f, b = 0.0f;
          if (soc <= 50.0f) {
            // 0..50 %: Rot → Gelb  (R konstant, G 0 → 180)
            r = 1.0f;
            g = (180.0f * (soc / 50.0f)) / 255.0f;
          } else {
            // 50..100 %: Gelb → Grün  (R 255 → 0, G 180 → 255)
            float t = (soc - 50.0f) / 50.0f;                // 0..1
            r = 1.0f - t;
            g = (180.0f + (255.0f - 180.0f) * t) / 255.0f; // 0.706 → 1.0
          }

          // 4) Helligkeit aus Slider EINMALIG hier auslesen und cachen.
          //    (Wuerden wir erst im addressable_set-Lambda lesen, koennte
          //     der Wert zwischen Berechnung und Setzen kippen -> Flackern
          //     und gelegentliche Fehlfarben.)
          id(calc_brightness) = id(led_helligkeit).state / 100.0f;

          // 5) Ergebnisse in Globals ablegen (für addressable_set-Lambdas)
          id(calc_active) = active;
          id(calc_r) = r;
          id(calc_g) = g;
          id(calc_b) = b;
      - if:
          condition:
            lambda: 'return id(calc_active) == 0;'
          then:
            # SOC < 10 %: LEDs 1..9 ausschalten. LED 0 wird vom
            # Blink-Intervall (siehe interval:-Block oben) toggelt.
            - light.addressable_set:
                id: soc_led_bar
                range_from: 1
                range_to:   9
                red:        0
                green:      0
                blue:       0
                color_brightness: 0.001
          else:
            # SOC >= 10 %: normale Anzeige
            # 1) aktive LEDs (0 .. active-1) in interpolierter Farbe setzen
            - light.addressable_set:
                id: soc_led_bar
                range_from: 0
                range_to:  !lambda 'return id(calc_active) - 1;'
                red:        !lambda 'return id(calc_r);'
                green:      !lambda 'return id(calc_g);'
                blue:       !lambda 'return id(calc_b);'
                # Helligkeit aus dem im Haupt-Lambda gecachten Wert
                # (Slider darf sich waehrend des Updates nicht auswirken)
                color_brightness: !lambda 'return id(calc_brightness);'
            # 2) Kurze Pause: RMT-Encoder Zeit geben, den aktiven Range zu
            #    rendern, bevor der Rest ueberschrieben wird. Bei 10 LEDs
            #    reichen 5 ms voellig aus, vermeidet aber Mischfarben,
            #    die bei zwei direkt aufeinander folgenden addressable_set-
            #    Aufrufen auftreten koennen.
            - delay: 5ms
            # 3) Verbleibende LEDs (active .. num_leds-1) explizit loeschen,
            #    damit sie bei fallendem SOC nicht weiter leuchten.
            #    Wird uebersprungen, wenn active == num_leds (sonst leerer Range).
            #    color_brightness: 0.001 (statt 0) ist explizit „nahezu aus"
            #    und loest den „global off"-Bug mancher RMT-LED-Treiber aus.
            - if:
                condition:
                  lambda: 'return id(calc_active) < ${led_count};'
                then:
                  - light.addressable_set:
                      id: soc_led_bar
                      range_from: !lambda 'return id(calc_active);'
                      range_to:   !lambda 'return ${led_count} - 1;'
                      red:        0
                      green:      0
                      blue:       0
                      color_brightness: 0.001

# -----------------------------------------------------------------------------
# LED-Streifen (esp32_rmt_led_strip – empfohlen mit ESP-IDF)
# -----------------------------------------------------------------------------
light:
  - platform: esp32_rmt_led_strip
    id: soc_led_bar
    name: "SOC LED Bar"
    pin: ${led_data_pin}
    num_leds: ${led_count}
    chipset: WS2812
    rgb_order: GRB
    restore_mode: ALWAYS_OFF

Es ist eigentlich alles wichtige entsprechend dokumentiert, falls dir was unklar ist dann einfach fragen!

Du kannst dir einen „Pseudo-SOC“ zum Testen anlegen:

Pseudo SOC Slider in Home Assistant einrichten

  1. In Home Assistant in Einstellungen

  2. Geräte & Dienste → oben rechts Helfer

  3. Unten rechts auf + Erstellen klicken → Zahlenwert Eingabe

  4. Felder ausfüllen:

Feld Wert
Name Pseudo SOC
Minimalwert 0
Maximalwert 100
Schrittweite 10
Einheit %
Anzeigemodus Schieberegler
  1. Auf Erstellen klicken – fertig!

  2. In der ESPHome YAML diese Zeile anpassen:
    (ist per default schon gesetzt!)

soc_entity_id: "input_number.pseudo_soc" 

Danach neu flashen → Slider in HA bewegen → LEDs reagieren sofort.

3 „Gefällt mir“

Bitte lass dir doch von der KI auch mal erklären wie man in einem Forum Code Tags richtig verwendet! :waving_hand:

1 „Gefällt mir“

hab’s angepasst - danke für den tipp
bin leider nicht so der Forum crack

1 „Gefällt mir“

Super, jetzt kann man es viel besser lesen und auch sauber und vor allen „funktional“ kopieren!

:+1:

1 „Gefällt mir“

super, vielen Dank

So könnte es dann in „echt“ ausschauen!

IMG_4969-ezgif.com-optimize

LED sind → Diese hier! ← . Viel Spaß beim Nachbauen! :wink:

1 „Gefällt mir“