Lambda To Turn On/Off Specific Neopixelbus LEDs

Greetings,

I am trying to adapt a 7-segment clock to be used with ESPHome. Eventually, I want to be able to use Home Assistant to update the brightness and color of each segment, but for now I am just trying to get basic functionality. Each LED ends up corresponding to a specific segment, and I believe I have the pixel mapper laid out correctly in my code.

Where I am getting stuck is when I go to have my lambda code turn on specific LEDs to illuminate specific segments that will display the time. I have tried a number of method after seeking advice from a few places, but none of them have worked so far and most end in compile errors (like my latest attempt below). Any help here would be appreciated.

light:
  - platform: neopixelbus
    type: GRB
    pin: D4
    num_leds: 25
    name: "Clock LEDs"
    id: clock_leds
    variant: WS2812X
    effects: []  # Disable built-in effects
script:
  - id: update_led_colors
    then:
      - lambda: |-
          auto now = id(sntp_time).now();
          if (!now.is_valid()) return;  // Ensure valid time

          int hours = now.hour % 12;
          if (hours == 0) hours = 12;
          int minutes = now.minute;

          // Function to get segment states for a digit
          auto get_segments = [](int digit) -> std::vector<bool> {
            switch(digit) {
              case 0: return {true, true, true, false, true, true, true};
              case 1: return {false, true, false, false, true, false, false};
              case 2: return {true, true, false, true, false, true, true};
              case 3: return {true, true, false, true, true, false, true};
              case 4: return {false, true, true, true, true, false, false};
              case 5: return {true, false, true, true, true, false, true};
              case 6: return {true, false, true, true, true, true, true};
              case 7: return {true, true, false, false, true, false, false};
              case 8: return {true, true, true, true, true, true, true};
              case 9: return {true, true, true, true, true, false, true};
              default: return {false, false, false, false, false, false, false};
            }
          };

          // Get segment states for each digit
          auto hour_ones_segments = get_segments(hours % 10);
          auto hour_tens_segments = get_segments(hours / 10);
          auto minute_ones_segments = get_segments(minutes % 10);
          auto minute_tens_segments = get_segments(minutes / 10);

          // Update LEDs on the light strip
          id(clock_leds)->addressable_lambda([=](auto &leds) {
            // Turn off all LEDs
            for (int i = 0; i < leds.size(); i++) {
              leds[i] = Color::BLACK;
            }

            // Helper to map segments to LEDs
            auto apply_segments = [&](int start_idx, const std::vector<bool> &segments) {
              for (size_t i = 0; i < segments.size(); i++) {
                if (segments[i]) {
                  leds[start_idx + i] = Color::WHITE;  // Turn on segment in white
                }
              }
            };

            // Apply segments for each digit
            apply_segments(0, hour_tens_segments);
            apply_segments(7, hour_ones_segments);
            apply_segments(14, minute_tens_segments);
            apply_segments(21, minute_ones_segments);
          });

The original project that I am attempting to use via ESPHome can be found here:

Have you looked at the light.addressable_set action? That will still need some lambda code but will make addressing the specific LEDs easier.

Thank you very much for replying!

I haven’t. So are you suggesting I come up with a bunch of addressable sets, such as “hour_1” through “hour_12” that defines which LEDs are on and off for each hour (and do the same for the minutes positions, I presume), then use my lambda simply to specify which of those addressable sets is applicable given the current time and activate them accordingly?

If so, would I call it via a lambda? I found this doc, but that doesn’t indicate if/how it can be done via lambda so I can parse out the current time.

I was actually thinking about doing something like that manually, so addressable_set would make things a bit easier if I am understanding it correctly above.

Edit:

In reading the doc closer, I think I’d need to break it out by digit (hourTens_1, hourOnes_0 - 9, minTens_1 - 5, and minOnes_0 - 9). In just not sure how I would call the correct one given the current time (or if one really understanding this at all, honestly).

Edit 2:

Hmm, upon further consideration I don’t think even what I wrote in my previous edit would work unless multiple ranges in the same addressable_set are supported as it’s possible to have LEDs off in the middle of those required to illuminate a given digit…

No, you’d break it out by segment. Set the brightness of each segment from a lambda that calculates it from the time and segment table. I’d probably create a function that takes a digit and segment index and returns a brightness value.

Group several actions each setting one segment in a script and call that every minute or whatever.

One drawback to this is that each segment will be updated separately, but I suspect it’s fast enough you won’t notice.

That was functionally what I was trying to do with the above array; set the LEDs’ states to on or off by using modulus math on the digits. The problem I am running into is figuring out how to address each LED from the script and then turn them on as applicable.

Ok, I was finally able to figure it out and it seems to be working fine at the moment. I would like to enhance the code to allow for changing brightness and color, but for now having those values hard-coded is at least functional.

I will post a link to my final code after I have had some time to clean it up a bit and comment it out better.