ESP32 RGB Platform Light with Optional Flash Indicator - How to?

I am new to ESPhome and am hoping someone can provide some help.

I have a working custom ESP32 based device on the RGB platform that I want to add an optional mode to flash the RGB light for special indication. Under normal circumstances, I would control the RGB light normally via either dashboard control and/or automations - when a special indication was desired, a “Flash RGB LED” switch would be turned ON and a special RGB color would be set in the ESP32 RGB device, which would then flash (~0.25Hz) this color in a sinusoidal fashion for as long as this “Flash RGB LED” switch was set. Once the “Flash RGB LED” was turned OFF, the device would return to the traditional RBG light platform.

I found an example that appears to flash an RGB light in ESPhome, though is instead based on the WS2812B chipset versus the RGB platform - the example is located here:

Pulsing an LED with ESPHome

I have tried to integrate this lambda code into my existing RGB code as follows:

esphome:
  name: motion-rgb-illuminance-62-84
  friendly_name: motion-rgb-illuminance-62-84

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  platform: esphome

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${friendly_name}_AP
    password: "password"

captive_portal:
    
web_server:
  include_internal: true  # This will show internal entities like sensors

switch:
  # Switch for flashing RBG LED
  - platform: gpio
    name: Flash RGB LED
    id: flash_rgb_led
    pin: GPIO32
    restore_mode: RESTORE_DEFAULT_OFF
    icon: mdi:pipe-valve

# Note Kingbright WP154A4SEJ3VBDZGW RBG LED has RBG vs RGB
output:
  - platform: ledc
    pin: GPIO2
    frequency: 25000 Hz
    id: led_red
  - platform: ledc
    pin: GPIO4
    frequency: 25000 Hz
    id: led_green
  - platform: ledc
    pin: GPIO0
    frequency: 25000 Hz
    id: led_blue

light:
  - platform: rgb
    name: "RGB LED"
    id: rgb_led
    red: led_red
    green: led_green
    blue: led_blue
    effects:
      - lambda:
          name: Flash LED
          update_interval: .05s
          lambda: |-
            if (id(flash_rgb_led).state) {
              static float in = 0.0;
              static float out = 0.0;    
              // Scale sin output from -1/1 to 0/1
              out = sin(in) * 0.5 + 0.5;
              auto call = id(rgb_led).turn_on();
              call.set_rgb(1.0, 0.0, 0.0);
              call.set_brightness(out);
              // Do not publish state to eliminate flooding of logs
              call.set_publish(false);
              call.set_save(false);
              call.perform();
              // RESOLUTION
              in += 0.0754;
              if (in > 6.283)
                in = 0;
            }

binary_sensor:
  - platform: gpio
    pin: GPIO13
    name: "PIR Sensor"
    device_class: motion

# See https://iambuschi.substack.com/p/diy-esphome-light-sensor
sensor:
  - platform: resistance
    sensor: source_sensor
    configuration: DOWNSTREAM
    resistor: 1kOhm
    name: "Light Sensor"

    unit_of_measurement: "lx"
    device_class: "illuminance"
    state_class: "measurement"
    accuracy_decimals: 1
    filters:
      - calibrate_linear:
         method: exact
         datapoints:
          - 170.0 -> 1076.4
          - 2000.0 -> 21.52
          - 21000.0 -> 0.0

  - platform: adc
    id: source_sensor
    pin: GPIO39
    attenuation: 11db
    
# Display IP, MAC, and other Wi-FI stats
text_sensor:
  - platform: wifi_info
    ip_address:
      name: ESP IP Address
    mac_address:
      name: ESP Mac Wifi Address
    ssid:
      name: ESP Connected SSID
    scan_results:
      name: ESP Latest Scan Results
    dns_address:
      name: ESP DNS Address

I initially commented out the “call.set_rgb(1.0, 0.0, 0.0);” command, however the brightness never changed. I removed this comment to test to see if I could even get RED to flash - only then could I occasionally get the RED to very, very slowly appear under certain circumstances (“Flash RGB LED” switch ON, “Flash LED” ON, ?) however it took several seconds to reach maximum RED brightness, and did not appear to cycle back to dim.

I suspect I am improperly blending WS2812B constructs with the RGB platform in addition to probably several other taboos based on my lack of understanding… This leads to many further questions:

  • Why is the flash frequency so slow?
  • Why does the flash only ramp up and not cycle between bright and dim?
  • Does “set_rbg” also have to be set within the “lamda” loop or can it just use the “global” RGB settings?
  • Is there a better way to accomplish my objective?

Thank you in advance.

Not sure what you mean with this.
Did you try pulse effect playing with transition_legth / update_interval?

I used the term “sinusoidal” to represent a gradual, smooth brightness cycling fade from light and dark versus cycling between On and Off - I prefer Sine wave brightness shifts versus Square wave or Triangular wave brightness changes. Thanks.

If you set transition length to x and update_interval to x+15%, you likely get that result…

I am not seeing how setting transition length and update_interval (+15%) will generate a sine wave output. Can you post code that you think addresses my solution? Thank you.

It doesn’t, but visually similar to it.
You can’t use light component to produce sine wave without considering gamma correction…
But I don’t see the point here, your eye don’t see pure sine wave light output as you would expect.

Worrying about gamma correction is overkill for this application - just trying to get a relatively sinusoidal brightness effect. I have used sine brightness effect multiple times in other projects (written in assembly language, ADA, C++, etc.) with great success - unfortunately, I am not fluent in ‘newer’ languages. Still hoping someone can offer a solution. Thanks.

Quite likely yes.
In case not, give transition a try.

Walked away, came back, and found the solution… a better reference:
Light Component - Lambda Effect

Removed the “Flash RGB LED” switch and instead using Effect option only to enable Flash - updated Effects section as follows:

    effects:
      - lambda:
          name: Flash LED
          update_interval: .05s
          lambda: |-
            static float in = 0.0;
            static float out = 0.0;  
            auto call = id(rgb_led).turn_on();
            // Transition of 50ms = 0.05s
            call.set_transition_length(50);
            // Scale sin output from -1/1 to 0/1
            out = sin(in) * 0.5 + 0.5;
            // call.set_rgb(1.0, 0.0, 0.0);
            call.set_brightness(out);
            // Do not publish state to eliminate flooding of logs
            call.set_publish(false);
            call.set_save(false);
            call.perform();
            // RESOLUTION
            in += 0.0754;
            if (in > 6.283)
              in = 0.0;

Can now also use this sensor to indicate when packages are delivered by flashing the corresponding carrier service color when they deliver a package:

  • US Mail Delivered – RGB (218, 41, 28)
  • Amazon Delivered – RGB (19, 153, 255)
  • UPS Delivered – RGB (250, 184, 10)
  • FedEx Delivered – RGB (102, 0, 153)

The Flash Effect is cleared and the RGB LED is returned to previous mode when the door to retrieve package(s) is opened…

ESPhome Sensor with Flash

Its always best if you just take a moment and test things in real life. This way there’s no misunderstand about what does what and also, if you have any preconceived thoughts about things then testing is a great way to correct for those too!

This is just me personally but, I find that if i setup circuits and test things then I have a far higher probability that ill remember those little things and it makes huge differences and often speeds up your future prototyping because you’ve used those things already and learned the basics so that i dont have to learn them now.

There are so many configuration options and then if you get into custom lambda effects then you might as well go ahead and plan on doing 10’s or dozens of refreshing that board to see how code changes actually look like in reality so, i wouldn’t even waste time refusing to try things because you assume it would or wouldn’t work a specific way… Just try things and then if its wrong then move on with confidence instead of regret.

Trust me on stuff like this too, I was a student under Mr. Miyagi and Daniel Son or you may know him as the Karate Kid but, in all honesty he punches like a girl so, Patience Grasshopper!!

Added four additional pseudo “flash” RGB lights (on unused pins) to allow storing up to four colors with individual brightness to be cycled through during the flash cycle. The “flash” colors and brightness will be included in the flash cycle by turning the corresponding pseudo “flash” RGB light ON while the “Flash LED” Effect is enabled on the primary “RGB LED”. Updated code:

esphome:
  name: motion-rgb-lumen-indicate-62-84
  friendly_name: motion-rgb-lumen-indicate-62-84

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  platform: esphome

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${friendly_name}_AP
    password: "password"

captive_portal:
    
web_server:
  include_internal: true  # This will show internal entities like sensors

# Note Kingbright WP154A4SEJ3VBDZGW RBG LED has RBG vs RGB
output:
  - platform: ledc
    pin: GPIO2
    id: led_red
  - platform: ledc
    pin: GPIO4
    id: led_green
  - platform: ledc
    pin: GPIO0
    id: led_blue
  # Use four psuedo RGB lights to store alternate flash colors
  - platform: ledc
    id: red1
    pin: GPIO12
  - platform: ledc
    id: green1
    pin: GPIO14
  - platform: ledc
    id: blue1
    pin: GPIO16
  - platform: ledc
    id: red2
    pin: GPIO17
  - platform: ledc
    id: green2
    pin: GPIO18
  - platform: ledc
    id: blue2
    pin: GPIO19
  - platform: ledc
    id: red3
    pin: GPIO21
  - platform: ledc
    id: green3
    pin: GPIO22
  - platform: ledc
    id: blue3
    pin: GPIO23
  - platform: ledc
    id: red4
    pin: GPIO25
  - platform: ledc
    id: green4
    pin: GPIO26
  - platform: ledc
    id: blue4
    pin: GPIO27

light:
  # Use four psuedo RGB lights to store alternate flash colors
  - platform: rgb
    name: "Flash1"
    id: flash1
    red: red1
    green: green1
    blue: blue1
  - platform: rgb
    name: "Flash2"
    id: flash2
    red: red2
    green: green2
    blue: blue2
  - platform: rgb
    name: "Flash3"
    id: flash3
    red: red3
    green: green3
    blue: blue3
  - platform: rgb
    name: "Flash4"
    id: flash4
    red: red4
    green: green4
    blue: blue4
  # Primary RGB light
  - platform: rgb
    name: "RGB LED"
    id: rgb_led
    red: led_red
    green: led_green
    blue: led_blue
    effects:
      - lambda:
            static bool flash1_on = 0;
            static bool flash2_on = 0;
            static bool flash3_on = 0;
            static bool flash4_on = 0;
            static int num_alternates = 0;
            static int step = 0;
            static float in = 0.0;
            static float out = 0.0;  
            static float r = id(rgb_led).remote_values.get_red();
            static float g = id(rgb_led).remote_values.get_green();
            static float b = id(rgb_led).remote_values.get_blue();
            static float l = id(rgb_led).remote_values.get_brightness();
            auto call = id(rgb_led).turn_on();
            // Transition of 50ms = 0.05s
            call.set_transition_length(50);
            // Scale and shift sin output from -1/1 to 0/1
            out = sin(in + 4.71239) * 0.5 + 0.5;
            call.set_brightness(out * l);
            in = in + 0.083776;
            if (in > 6.2832)
            {
              in = 0.0;
              flash1_on = (id(flash1).remote_values.is_on() > 0);
              flash2_on = (id(flash2).remote_values.is_on() > 0);
              flash3_on = (id(flash3).remote_values.is_on() > 0);
              flash4_on = (id(flash4).remote_values.is_on() > 0);
              num_alternates = 0;
              if (flash1_on) num_alternates = num_alternates + 1; 
              if (flash2_on) num_alternates = num_alternates + 1;
              if (flash3_on) num_alternates = num_alternates + 1;
              if (flash4_on) num_alternates = num_alternates + 1;
              if ( (step == 0) && (num_alternates > 0) )
              { 
                step = 1;
              }
              if ( (step > 0) && (num_alternates == 0) )
              {
                step = 0;
              }
              if (step == 1)
              {
                if (flash1_on)
                {
                  r = id(flash1).remote_values.get_red();
                  g = id(flash1).remote_values.get_green();
                  b = id(flash1).remote_values.get_blue();
                  l = id(flash1).remote_values.get_brightness();
                }
                else if (flash2_on)
                {
                  r = id(flash2).remote_values.get_red();
                  g = id(flash2).remote_values.get_green();
                  b = id(flash2).remote_values.get_blue();
                  l = id(flash2).remote_values.get_brightness();
                }
                else if (flash3_on)
                {
                  r = id(flash3).remote_values.get_red();
                  g = id(flash3).remote_values.get_green();
                  b = id(flash3).remote_values.get_blue();
                  l = id(flash3).remote_values.get_brightness();
                }
                else if (flash4_on)
                {
                  r = id(flash4).remote_values.get_red();
                  g = id(flash4).remote_values.get_green();
                  b = id(flash4).remote_values.get_blue();
                  l = id(flash4).remote_values.get_brightness();
                }
              }
              else if (step == 2)
              {
                if ( flash2_on && flash1_on )
                {
                  r = id(flash2).remote_values.get_red();
                  g = id(flash2).remote_values.get_green();
                  b = id(flash2).remote_values.get_blue();
                  l = id(flash2).remote_values.get_brightness();
                }
                else if ( flash3_on && (flash2_on || flash1_on) )
                {
                  r = id(flash3).remote_values.get_red();
                  g = id(flash3).remote_values.get_green();
                  b = id(flash3).remote_values.get_blue();
                  l = id(flash3).remote_values.get_brightness();
                }
                else if (flash4_on)
                {
                  r = id(flash4).remote_values.get_red();
                  g = id(flash4).remote_values.get_green();
                  b = id(flash4).remote_values.get_blue();
                  l = id(flash4).remote_values.get_brightness();
                }
              }
              else if (step == 3)
              {
                if (flash3_on && flash2_on && flash1_on)
                {
                  r = id(flash3).remote_values.get_red();
                  g = id(flash3).remote_values.get_green();
                  b = id(flash3).remote_values.get_blue();
                  l = id(flash3).remote_values.get_brightness(); 
                }
                else if (flash4_on)
                {
                  r = id(flash4).remote_values.get_red();
                  g = id(flash4).remote_values.get_green();
                  b = id(flash4).remote_values.get_blue();
                  l = id(flash4).remote_values.get_brightness();  
                }
              }
              else if (step == 4)
              {
                r = id(flash4).remote_values.get_red();
                g = id(flash4).remote_values.get_green();
                b = id(flash4).remote_values.get_blue();
                l = id(flash4).remote_values.get_brightness();  
              }
              else
              {
                r = id(rgb_led).remote_values.get_red();
                g = id(rgb_led).remote_values.get_green();
                b = id(rgb_led).remote_values.get_blue();
                l = id(rgb_led).remote_values.get_brightness();
              }
              step = step + 1;
              if ( step > num_alternates)
              {
                step = 1;
              }
            }
            call.set_rgb(r, g, b);
            // Do not publish state to eliminate flooding of logs
            call.set_publish(false);
            call.set_save(false);
            call.perform();

binary_sensor:
  - platform: gpio
    pin: GPIO13
    name: "PIR Sensor"
    device_class: motion

# See https://iambuschi.substack.com/p/diy-esphome-light-sensor
sensor:
  - platform: resistance
    sensor: source_sensor
    configuration: DOWNSTREAM
    resistor: 1.0kOhm
    name: "Light Sensor"
    unit_of_measurement: "lx"
    device_class: "illuminance"
    state_class: "measurement"
    accuracy_decimals: 1
    filters:
      - calibrate_linear:
         method: exact
         datapoints:
          - 170.0 -> 1076.4
          - 2000.0 -> 21.52
          - 21000.0 -> 0.0
  - platform: adc
    id: source_sensor
    pin: GPIO39
    attenuation: 12db
    update_interval: 60s