ESP32-C3, WS2812, light | effects | <demo> works, my lamba code not so much

This is my first attempt at working with an ESP32-C3, a WS2812 led strip (12 LEDs), and an LD2410C mmWave sensor.

Problem statement:

  • By detecting human presence near a sink, which is far from the water heater, I can momentarily turn on a small hot-water recirculation pump. Indicate the return water temperature from cold (blue) to hot (red) near the sink.

Plan:

  • Node 1 near the sink, detects human presence and controls WS2812 LED strip.
  • Node 2 near the water heater, controls the recirculation pump.

Status:

  • Presence detection is working fine, so I’m not focused on that right now.
  • My problem is I can’t seem to control the LED strip reliably, yet the ‘out of the box’ demos seem to work fine.

I have a lights | effects section in my yaml. Getting the demo ‘effects’ to work was about as easy as copy/paste. I couldn’t find a cold to hot fader in pure yaml, so I’ve been trying to create a lambda function to do what I need.

I created 3 lambda functions to use each to try to learn how to make it work. I can only say, “they sometimes work” and sometimes they don’t - and I haven’t figured out why.

What I observe:

  • effects: random, strobe, addressable_rainbow, addressable_scan, addressable_twinkle and addressable_color_wipe all seem to work fine.
  • one of my 3 lambda attempts works pretty reliably, and the other two “occasionally” work, but not reliably.
  • I also get a bit of flicker when my lambda’s are running.

yaml

light:
  - platform: status_led
    name: ESP32C_Status
    pin:
      number: GPIO8
      inverted: True
    internal: False
    restore_mode: ALWAYS_OFF
  
  - platform: neopixelbus
    id: temperature_led
    name: "LED Strip"
    type: GRBW
    variant: WS2812
    pin: GPIO5
    num_leds: 12
    effects:
      - random:
      - strobe:
      - addressable_rainbow:
      - addressable_scan:
      - addressable_twinkle:
      - addressable_color_wipe:
          name: "Color Wipe"
          add_led_interval: 0.1s
          colors:
            - red: 100%
              green: 0%
              blue: 0%
              white: 0%
              num_leds: 12
            - red: 0%
              green: 100%
              blue: 0%
              white: 0%
              num_leds: 12
            - red: 0%
              green: 0%
              blue: 100%
              white: 0%
              num_leds: 12
      - lambda:
          # This one works, including picking up the intensity from the web slider
          # The logi function reports 
          #    [16:06:42][I][rgbw:300]: SEQU: 0.00,0.00,0.25,0.00,  0.67
          #    [16:06:43][I][rgbw:300]: SEQU: 0.00,0.00,0.00,0.25,  0.67
          #    [16:06:44][I][rgbw:300]: SEQU: 0.25,0.25,0.25,0.00,  0.67
          #    [16:06:45][I][rgbw:300]: SEQU: 0.25,0.00,0.00,0.00,  0.67
          #    [16:06:46][I][rgbw:300]: SEQU: 0.00,0.25,0.00,0.00,  0.67
          #    [16:06:47][I][rgbw:300]: SEQU: 0.00,0.00,0.25,0.00,  0.67
          #    [16:06:48][I][rgbw:300]: SEQU: 0.00,0.00,0.00,0.25,  0.67
          name: Custom - RGBW Sequence
          update_interval: 1s
          lambda: |-
             static int state = 0;
             auto br = id(intensity_channel).state/100;
             float r,g,b,w;
             if (state == 0) {
               r = 0.25; g = 0.25; b = 0.25; w = 0.0;
             } else if (state == 1) {
               r = 0.25; g = 0.0; b = 0.0; w = 0.0;
             } else if (state == 2) {
               r = 0.0; g = 0.25; b = 0.0; w = 0.0;
             } else if (state == 3) {
               r = 0.0; g = 0.0; b = 0.25; w = 0.0;
             } else {
               r = 0.0; g = 0.0; b = 0.0; w = 0.25;
             }
             ESP_LOGI("rgbw", "SEQU: %3.2f,%3.2f,%3.2f,%3.2f,  %3.2f", r,g,b,w, br);
             auto call = id(temperature_led).turn_on();
             call.set_transition_length(500);
             call.set_rgbw(r,g,b,w);
             call.set_color_brightness(br);
             call.perform();
             state += 1;
             if (state == 5)
               state = 0;
      - lambda:
          # This one does not work. The sliders, even the intensity are picked up
          # BUT the led strip usually does not update (and sometimes it does).
          # 
          # The logi function reports 
          #    [16:07:07][I][rgbw:300]: SEQU: 0.00,0.00,0.25,0.00,  0.67
          #    [16:07:08][I][rgbw:300]: SEQU: 0.00,0.00,0.00,0.25,  0.67
          #    [16:07:09][I][rgbw:300]: SEQU: 0.25,0.25,0.25,0.00,  0.67
          #    [16:07:10][I][rgbw:300]: SEQU: 0.25,0.00,0.00,0.00,  0.67
          #    [16:07:11][I][rgbw:300]: SEQU: 0.00,0.25,0.00,0.00,  0.67
          #    [16:07:12][I][rgbw:300]: SEQU: 0.00,0.00,0.25,0.00,  0.67
          #    [16:07:13][I][rgbw:300]: SEQU: 0.00,0.00,0.00,0.25,  0.67
          #    [16:07:14][I][rgbw:300]: SEQU: 0.25,0.25,0.25,0.00,  0.67
          name: Custom - RGBW
          update_interval: 0.5s
          lambda: |- 
             float br = id(intensity_channel).state/100;
             float r = id(red_channel).state/100;
             float g = id(green_channel).state/100;
             float b = id(blue_channel).state/100;
             float w = id(white_channel).state/100;
             ESP_LOGI("rgbw", "RGBW: %3.2f,%3.2f,%3.2f,%3.2f,  %3.2f", r,g,b,w, br);
             auto call = id(temperature_led).turn_on();
             call.set_rgbw(r,g,b,w);
             call.set_color_brightness(br);
             call.perform();
      - lambda:
          # This one does not work. The sliders, even the intensity, are picked up
          # BUT the led strip usually does not update (and sometimes it does).
          #
          # The logi function reports 
          #   [16:11:03][I][temp:346]: Temp: 1.00,0.00,0.00,0.00,  0.87, 130.0°F
          #   [16:11:04][I][temp:346]: Temp: 1.00,0.29,0.00,0.00,  0.87, 120.0°F
          #   [16:11:04][I][temp:346]: Temp: 1.00,0.71,0.00,0.00,  0.87, 105.0°F
          #   [16:11:05][I][temp:346]: Temp: 0.60,0.60,0.40,0.00,  0.87, 81.0°F
          #   [16:11:05][I][temp:346]: Temp: 0.60,0.60,0.40,0.00,  0.87, 81.0°F
          #   [16:11:06][I][temp:346]: Temp: 0.26,0.26,0.74,0.00,  0.87, 69.0°F
          #   [16:11:06][I][temp:346]: Temp: 0.26,0.26,0.74,0.00,  0.87, 69.0°F
          name: Custom - Temp
          update_interval: 0.5s
          lambda: |- 
             float temp_f = id(water_temp).state;
             float br = id(intensity_channel).state / 100.0f;
             const float tCold = 60.0; 
             const float tWarm = 95.0;
             const float tHot = 130.0;
             const float w = 0.0f;                  // white is off
             
             if (temp_f < tCold) temp_f = tCold;
             if (temp_f > tHot) temp_f = tHot;
             float r, g, b;
             if (temp_f <= tWarm) {
               r = (1.0f * (temp_f - tCold)) / (tWarm - tCold);
               g = (1.0f * (temp_f - tCold)) / (tWarm - tCold);
               b = 1.0f - ((1.0f * (temp_f - tCold)) / (tWarm - tCold));
             } else {
               r = 1.0f;
               g = 1.0f - (temp_f - tWarm) / (tHot - tWarm);
               b = 0.0f;
             }
             ESP_LOGI("temp", "Temp: %3.2f,%3.2f,%3.2f,%3.2f,  %3.2f, %3.1f°F", r,g,b,w, br, temp_f);
             auto call = id(temperature_led).turn_on();
             call.set_rgbw(r,g,b,w);
             call.set_color_brightness(br);
             call.perform();

number:
  - platform: template
    name: A- Water Temperature
    id: water_temp
    optimistic: true
    mode: slider
    min_value: 60
    max_value: 140
    step: 1
  - platform: template
    name: A- Color Red
    id: red_channel
    optimistic: true
    mode: slider
    min_value: 0
    max_value: 100
    step: 1
  - platform: template
    name: A- Color Green
    id: green_channel
    optimistic: true
    mode: slider
    min_value: 0
    max_value: 100
    step: 1
  - platform: template
    name: A- Color Blue
    id: blue_channel
    optimistic: true
    mode: slider
    min_value: 0
    max_value: 100
    step: 1
  - platform: template
    name: A- Color A-Intensity
    id: intensity_channel
    optimistic: true
    mode: slider
    min_value: 0
    max_value: 100
    step: 1
  - platform: template
    name: A- Color White
    id: white_channel
    optimistic: true
    mode: slider
    min_value: 0
    max_value: 100
    step: 1

Bonus item - I’ve seen some projects with a color wheel, but I haven’t found enough examples and code to duplicate that - if you can point me to a project.