Writing multiple sections of an addressable LED strip at once possible?

I built a ceiling light to extend the ambilight of my TV. As I am using an integrated Sonoff Basic R3 for turning the relay for the power supply on and off as well as controlling SK6812WWA LEDs in the bottom am the SK6812 RGBW LEDs in the ring going with ESPHome seemed to be the best solution to integrate it in my setup.

I get the ambilight information as json-file from the TV. Right now json is read by HomeAssistant, processed and RGB information is sent to the ESP. The issue with this is that I have to split the LED strip in ESPHome in partitions to have individual light instances in HomeAssistant. That’s why I limited it to 6 sections:

It works, but has some delay, which is acceptable because it only lights up the upper room which is not in direct vision but gives a nice immersive feeling.

Now to improve delays, and address finer sections of the LED strip I want to integrate as much as possible towards the ESP and make the processing independent of HomeAssistant. Therefore, json parsing and mapping onto the LED strip has to happen on the ESP. I have that basically working with this code:

  - platform: neopixelbus
    id: wz_rgbw
    name: "WZ RGB Licht"
    variant: SK6812
    type: GRBW
    method: ESP8266_DMA
    pin: GPIO3
    num_leds: 96
    on_turn_on:
      ...
    on_turn_off:
      ...
    effects:
    - addressable_lambda:
        name: "ambilight"
        update_interval: 1s
        lambda: |-
            WiFiClient wifiClient;
            HTTPClient http;
            ESP_LOGD("ambi", "started lambda");
            http.useHTTP10(true);
            http.begin(wifiClient, "http://internalip/1/ambilight/measured");
            int htstt = http.GET();
            if(htstt <= 0) {
              http.end();
              ESP_LOGD("ambi", "couldnt connect to json, wait and rety");
              delay(1000);
            }
            ESP_LOGD("ambi", "got json");
            DynamicJsonDocument doc(2000);
            deserializeJson(doc, http.getStream());
            ESP_LOGD("ambi", "buffered json");
            it.range(0, 5)   = Color(doc["layer1"]["top"]["8"]["r"].as<int>(),  doc["layer1"]["top"]["8"]["g"].as<int>(),  doc["layer1"]["top"]["8"]["b"].as<int>());
            it.range(5, 10)  = Color(doc["layer1"]["top"]["7"]["r"].as<int>(),  doc["layer1"]["top"]["7"]["g"].as<int>(),  doc["layer1"]["top"]["7"]["b"].as<int>());
            it.range(10, 15) = Color(doc["layer1"]["top"]["6"]["r"].as<int>(),  doc["layer1"]["top"]["6"]["g"].as<int>(),  doc["layer1"]["top"]["6"]["b"].as<int>());
            it.range(15, 21) = Color(doc["layer1"]["top"]["5"]["r"].as<int>(),  doc["layer1"]["top"]["5"]["g"].as<int>(),  doc["layer1"]["top"]["5"]["b"].as<int>());
            it.range(21, 27) = Color(doc["layer1"]["top"]["4"]["r"].as<int>(),  doc["layer1"]["top"]["4"]["g"].as<int>(),  doc["layer1"]["top"]["4"]["b"].as<int>());
            it.range(27, 33) = Color(doc["layer1"]["top"]["3"]["r"].as<int>(),  doc["layer1"]["top"]["3"]["g"].as<int>(),  doc["layer1"]["top"]["3"]["b"].as<int>());
            ...
            ESP_LOGD("ambi", "wrote rgb");
            http.end();
            ESP_LOGD("ambi", "wait 1sec");
            delay(1000);

The issue is that this doesn’t use transitions and makes the lights flash to the next state with each iteration. Is there a possibility to add the transition information or define a default before that is considered in this assignment?

I guess another way that supports transitions would be something like:

auto call = id(wz_rgbw).make_call();
call.set_brightness(0.72);
call.set_transition_length(800);
call.set_rgb(doc["layer1"]["left"]["0"]["r"].as<int>(),doc["layer1"]["left"]["0"]["g"].as<int>(),doc["layer1"]["left"]["0"]["b"].as<int>());
call.perform();

This way multiple conditions can be set and activated at once with perform(). But this would require to split the addressable light up into partitions and I could only set one segment at a time. I would need 17 segments. Something like a “call.set_range” would do what I’m looking for.

Is there a possibility to write different sections of an addressable LED strip and then “active” the values at one with a transition?

Thanks for any hints and ideas!

Not sure if it helps but I think WLED can interact with Hyperion to produce what you want, You can set up mod=ore than one sector of LEDs in WLED. Never tried it but it might help. WLED | Hyperion

Thanks, I was looking into WLED as an alternative 1 year ago when I first started the project. Back then WLED could only address 1 light-strip. As I have 2 light-strips that didn’t work. Also, I have to switch on and off the power supply with the ESP. But it looks like this can be done with WLED also now.
But I don’t have Hyperion. This is a not so new Philips TV which exposed the integrated LED backlight information over a json API, called jointspace.
In the end going to WLED could be an option. Especially because it seems to be made for more realtime applications. But it would require one more software package. So I guess I keep it as a last resort :slight_smile:

I guess this is exactly what you’re looking for:

It can be installed as an “addon” ( Usermod) on WLED, so you’d get direct TV (jointspace) → WLED without having to code anything

Thanks! Seems WLED is the preferred way for these tasks. :grin: I looked into the usermod.h. It uses the same way of getting the data and processing it with ArduinoJson as DynamicJsonDocument. The comment there states that following I’d have to map the results to WLED segments. So in the end the same I try to achieve in ESPHome but WLED then seems to support the parallel write with transitions which is what I am currently missing in ESPHome.

To not give up on ESPHome yet I tried to set up the 17 individual partitions in ESPHome. It fits into the memory of the tiny ESP-01 (crazy, isn’t it?). I could imagine a structure as follows:

  • input button → can be controlled via HomeAssistant, enables ambilight mode
  • custom “json_sensor” that if input_button=on constantly pulls and processes json data
  • custom effect that is enabled with input_button=on for each segment that constantly writes only its individual rgb information of the json_sensor to the LEDs using the initially mentioned “call”-commands
    try to define an additional sensor that.

For this I only need to figure out what options I have to store multiple rgb-values in a single sensor/variable.

Edit: I continued on that idea and have a first POC. To apply the json data to multiple segments I store the json data in a global. I need to store a total of 51 integers (17xRGB). Not sure what would be the most memory efficient. For now, I do it like so:

globals:
  - id: json_data
    type: int[51]
    restore_value: no
    initial_value: '{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}'

Right now I need to use 2 switches. One input, one helper (issue described at the end):

switch:
  - platform: template
    id: ambi_switch
    name: "ambi switch"
    turn_on_action:
      - delay: 1s
      - switch.turn_on: loop_helper
      - script.execute: json_get
    turn_off_action:
      - switch.turn_off: loop_helper
  - platform: template
    id: loop_helper
    optimistic: true

Script that runs in a loop and gets the json data, writes it into the array. I’m no C++ programmer, so not sure how to do that more efficient than writing every integer individually:

script:
  - id: json_get
    mode: single
    then:
    - logger.log: "entering json script"
    - while:
        condition:
          switch.is_on: loop_helper  # runs while ambilight data is asked for
        then:
          - lambda: |-
              ESP_LOGD("ambi", "started lambda");
              WiFiClient wifiClient;
              HTTPClient http;
              http.useHTTP10(true);
              http.begin(wifiClient, "http://internalip:1925/1/ambilight/processed");
              int htstt = http.GET();
              if(htstt <= 0) {
                http.end();
                ESP_LOGD("ambi", "couldnt connect to json, wait and rety");
                delay(1000);  // wait and retry
              }
              ESP_LOGD("ambi", "got json");
              DynamicJsonDocument doc(2000);
              deserializeJson(doc, http.getStream());
              ESP_LOGD("ambi", "memory usage: %d", doc.memoryUsage());
              http.end();
              id(json_data)[0]  = doc["layer1"]["top"]["8"]["r"].as<int>();
              id(json_data)[1]  = doc["layer1"]["top"]["8"]["g"].as<int>();
              ...
              id(json_data)[49] = doc["layer1"]["right"]["0"]["g"].as<int>();
              id(json_data)[50] = doc["layer1"]["right"]["0"]["b"].as<int>();
              doc.clear();
              ESP_LOGD("ambi", "stored json, wait 1sec");
              delay(1000);

Light now has the “master” and partitions. For the sake of simplicity I only included 1 partition:

light:
  - platform: partition
    name: "Partition Light rrf"
    id: rrf
    segments:
      - id: wz_rgbw
        from: 0
        to: 4
    effects:
    - addressable_lambda:
        name: "ambilight"
        update_interval: 1s
        lambda: |-
            auto call = id(rrf).make_call();
            call.set_brightness(0.72);
            call.set_transition_length(800);
            call.set_rgb(id(json_data)[0],id(json_data)[1],id(json_data)[2]);
            call.perform();
            ESP_LOGD("ambi", "wrote rgb rrf");
            //delay(1000);
  - platform: neopixelbus
    id: wz_rgbw
    name: "WZ RGB Licht"
    variant: SK6812
    type: GRBW
    method: ESP8266_DMA
    pin: GPIO3
    num_leds: 96

This seems to work in principle, but seem to hit memory limits. The following issues/questions persist:

  • As is the ESP crashes after 30 seconds when the json-storing script is running. Am I missing something and not reassigning my values to “the same memory region”?
  • I double-checked with the doc.memoryUsage() function that the buffer isn’t increasing. But also there I might just somehow create multiples? I also tried with and w/o doc.clear(). It doesn’t seem to make a difference.
  • The script loop is still somewhat broken. It should run in a loop as long as my input_switch is high. But if I don’t use the loop_helper switch it isn’t working. In that case the switch directly gets disabled again and the while-condition breaks. Delays up to 2sec don’t change that. With the helper it works but the ambi_switch turns off again instantly but helper stays on.
    Not giving up yet :slight_smile: