Light schedule with dimming on ESPHome

Hi all,

I have recently got a Shelly RGBW2 to control my fish tank’s RGBW led strip.
I want it to ramp up the brightness and dim during the course of the day - I’ve done something like that in arduino ages ago, however ESPHome/yaml is new to me.

Not sure how reliable it is, but I got the scheduling bit of code from ChatGPT and it seems to make sense. What i’m struggling now is integrating my current code with the scheduling code. Any chance someone could give me some pointers ?

My current ESPHome code:


substitutions:
  device_name: shelly

esphome:
  name: ${device_name}
  platform: ESP8266
  board: esp01_1m

logger:
  level: DEBUG

api:
ota:

web_server:
  port: 80


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none
  reboot_timeout: 5min

  ap:
    ssid: "${device_name} FB"
    password: !secret Fallback_Hotspot  
    
  # Optional manual IP
  manual_ip:
    static_ip: 192.168.1.181
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    
time:
  - platform: sntp
    id: sntp_time


sensor:
  - platform: wifi_signal
    name: "${device_name} - WiFi Signal"
    update_interval: 60s
  - platform: uptime
    name: "${device_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s
  - platform: uptime
    name: "${device_name} - Uptime Seconds"
    id: uptime_sensor
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? String(days) + "d " : "") +
                (hours ? String(hours) + "h " : "") +
                (minutes ? String(minutes) + "m " : "") +
                (String(seconds) + "s")
              ).c_str();
  - platform: adc
    pin: A0
    name: "${device_name} - adc"
    update_interval: 1s
    accuracy_decimals: 4
    id: current_raw
    internal: true
  - platform: template
    name: "${device_name} - Power"
    unit_of_measurement: W
    accuracy_decimals: 1
    device_class: power
    update_interval: 10s
    id: my_power
    lambda: return id(current_raw).state;
    filters:
      - calibrate_linear:
        - 0.3701 -> 0.0
        - 0.37891 -> 0.8687
        - 0.4169 -> 1.7375
      - multiply: 24 # 24V lamp

  - platform: total_daily_energy
    name: "${device_name} - Total Daily Energy"
    power_id: my_power
    filters:
          # Multiplication factor from W to kW is 0.001
          - multiply: 0.001
    unit_of_measurement: kW




light:
  - platform: monochromatic
    name: ${device_name}_R
    id: ${device_name}_R
    output: ${device_name}_out_R
    restore_mode: RESTORE_DEFAULT_OFF
  - platform: monochromatic
    name: ${device_name}_G
    id: ${device_name}_G
    output: ${device_name}_out_G
    restore_mode: RESTORE_DEFAULT_OFF
  - platform: monochromatic
    name: ${device_name}_B
    id: ${device_name}_B
    output: ${device_name}_out_B
    restore_mode: RESTORE_DEFAULT_OFF
  - platform: monochromatic
    name: ${device_name}_W
    id: ${device_name}_W
    output: ${device_name}_out_W
    restore_mode: RESTORE_DEFAULT_ON
  


output:
  - platform: esp8266_pwm
    id: ${device_name}_out_R
    pin: GPIO12
    frequency: 1000 Hz
  - platform: esp8266_pwm
    id: ${device_name}_out_G
    pin: GPIO15
    frequency: 1000 Hz
  - platform: esp8266_pwm
    id: ${device_name}_out_B
    pin: GPIO14
    frequency: 1000 Hz
  - platform: esp8266_pwm
    id: ${device_name}_out_W
    pin: GPIO4
    frequency: 1000 Hz
    
text_sensor:
  - platform: template
    name: "${device_name} - Uptime"
    id: uptime_human
    icon: mdi:clock-start
    update_interval: 60s
  - platform: version
    name: "${device_name} - ESPHome Version"
  - platform: wifi_info
    ip_address:
      name: "${device_name} - IP"


switch:
  - platform: restart
    name: "${device_name} - Restart"


binary_sensor:
  - platform: gpio
    name: '${device_name}_switch'
    pin:
      number: GPIO5

ChatGPT’s suggested implementation to control some lights ramping up/down brightness between 3 intervals:

esphome:
  name: my_lights
  platform: ESP8266
  board: nodemcu
  logger:
    level: DEBUG

wifi:
  ssid: "your_wifi_ssid"
  password: "your_wifi_password"
api:
web_server:

light:
  - platform: template
    name: "Light A"
    id: light_a
    lambda: |-
      auto now = time(nullptr);
      int total_minutes = now.tm_hour * 60 + now.tm_min;
      float brightness = 0;
      int start_minutes_1 = 10 * 60;
      int end_minutes_1 = 15 * 60;
      int start_minutes_2 = 15 * 60;
      int end_minutes_2 = 19 * 60;
      int start_minutes_3 = 19 * 60;
      int end_minutes_3 = 22 * 60;
      int duration_1 = end_minutes_1 - start_minutes_1;
      int duration_2 = end_minutes_2 - start_minutes_2;
      int duration_3 = end_minutes_3 - start_minutes_3;
      float max_brightness_1 = 50.0;
      float max_brightness_2 = 80.0;
      if (total_minutes >= start_minutes_1 && total_minutes < end_minutes_1) {
          brightness = total_minutes - start_minutes_1;
          brightness = brightness / duration_1 * max_brightness_1;
      } else if (total_minutes >= start_minutes_2 && total_minutes < end_minutes_2) {
          brightness = total_minutes - start_minutes_2 + duration_1;
          brightness = (brightness / duration_2) * (max_brightness_2 - max_brightness_1) + max_brightness_1;
      } else if (total_minutes >= start_minutes_3 && total_minutes < end_minutes_3) {
          brightness = total_minutes - start_minutes_3 + duration_1 + duration_2;
          brightness = (duration_1 + duration_2 + duration_3 - brightness) / duration_3 * max_brightness_2;
      }
      return brightness;

I tried putting the lambda in so many places, but keep getting yaml indentation errors.

Would really appreciate some help.

Thanks!

ChatGPT is a bit of a touchy topic around here at the moment.
https://community.home-assistant.io/t/want-to-help-others-leave-your-ai-at-the-door/523704/186

It gives very wrong answers that superficially look ok.

For example, I don’t believe there is such thing as an ESPHome template light. That’s a major fail.

Uh’oh…thanks for pointing this out. If I put ChatGPT aside, is there a way to achieve it using native functions ? E.g. Between 10:00 and 15:00 increase brightness from 0% to 80%, keep it at 80% for a few hours, then gradually fade out between, say, 20:00-22:00. I currently have HA automations for smth similar, however I don’t like that it’s network dependent.

Thank you

Here is a very rough starting point, which should compile, which I think you should be able to refine to meet your needs.

time:
  - platform: sntp
    id: sntp_time
    
    
number:
  - platform: template
    id: test_brightness
    icon: mdi:target-variant
    name: "Brightness"
    update_interval: 5s
    step: 1
    min_value: 0
    max_value: 100
    set_action:
       then:
        - delay: 1s
        #You'll need to work on the below
        # - light.turn_on:
            # id: light_1
            # brightness: !lambda |-
              # // output value must be in range 0 - 1.0
              # return x / 100.0;
    lambda: |-
      auto time_now = id(sntp_time).now();
      int total_minutes = time_now.second *60; //Comment this out after testing. It uses seconds so you can see changes quicker.
      // int total_minutes = time_now.hour * 60 + time_now.minute;
      float brightness = 0;
      int start_minutes_1 = 10 * 60;
      int end_minutes_1 = 20 * 60;
      int start_minutes_2 = 20 * 60;
      int end_minutes_2 = 40 * 60;
      int start_minutes_3 = 40 * 60;
      int end_minutes_3 = 45 * 60;
      int duration_1 = end_minutes_1 - start_minutes_1;
      int duration_2 = end_minutes_2 - start_minutes_2;
      int duration_3 = end_minutes_3 - start_minutes_3;
      float max_brightness_1 = 50.0;
      float max_brightness_2 = 80.0;
      if (total_minutes >= start_minutes_1 && total_minutes < end_minutes_1) {
          brightness = total_minutes - start_minutes_1;
          brightness = brightness / duration_1 * max_brightness_1;
      } else if (total_minutes >= start_minutes_2 && total_minutes < end_minutes_2) {
          brightness = total_minutes - start_minutes_2 + duration_1;
          brightness = (brightness / duration_2) * (max_brightness_2 - max_brightness_1) + max_brightness_1;
      } else if (total_minutes >= start_minutes_3 && total_minutes < end_minutes_3) {
          brightness = total_minutes - start_minutes_3 + duration_1 + duration_2;
          brightness = (duration_1 + duration_2 + duration_3 - brightness) / duration_3 * max_brightness_2;
      }
      return brightness;

2 Likes

I am NO EXPERT, but… why not just do it in a Home Assistant automation?

The usual motivation is because it’s more reliable for it to be totally self-contained on the device in case wifi / HA server etc goes down.

Thanks for the code above Mahko_Mahko, will give this a go.

1 Like

Thanks again Mahko_mahko for your help, but I’m a little stuck now. I have managed to get the brightness value to behave as I want it, however I am struggling to feed that value as brightness of a light.

What confuses me is the on_action bit. I feel like on_value would make more sense there, however it’s forcing me to use optimistic: true, which does not allow me to use lambdas. I tried getting the brightness value from the scheduling lambda to get written into a global variable (to be used with on_state with return {global_brightness = brightness; } , but it wouldn’t compile.

Any pointers in how to get that return brightness value transferred into the brightness of my led ?

esphome:
  name: esp-test

esp32:
  board: esp32dev


# Enable logging
logger:

# Enable Home Assistant API
api:


ota:


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.169
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.1
    dns2: 192.168.1.2
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp-Test Fallback Hotspot"
    password: "ZsDsw6T6JnAp"

captive_portal:


web_server:
  port: 80


time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/London

output:
  - platform: ledc
    id: led_output
    pin: GPIO16




light:
  - platform: monochromatic
    name: LED
    id: led
    output: led_output
    default_transition_length: 0s




number:
  - platform: template
    id: test_brightness
    icon: mdi:target-variant
    name: "Brightness"
    update_interval: 1s
    step: 1
    min_value: 0
    max_value: 100
    set_action:
       then:
        - light.turn_on:
            id: led
            brightness: !lambda |-
              return x / 100.0;



    lambda: |-
      auto time_now = id(sntp_time).now();
      int total_minutes = time_now.second *60; //Comment this out after testing. It uses seconds so you can see changes quicker.
      //int total_minutes = time_now.hour * 60 + time_now.minute;
      float brightness = 0;
      int start_minutes_1 = 0 * 60;
      int end_minutes_1 = 10 * 60;
      int start_minutes_2 = 10 * 60;
      int end_minutes_2 = 30 * 60;
      int start_minutes_3 = 30 * 60;
      int end_minutes_3 = 60 * 60;
      int duration_1 = end_minutes_1 - start_minutes_1;
      int duration_2 = end_minutes_2 - start_minutes_2;
      int duration_3 = end_minutes_3 - start_minutes_3;
      float max_brightness_1 = 100.0;
      float max_brightness_2 = 100.0;
      if (total_minutes >= start_minutes_1 && total_minutes < end_minutes_1) {
          brightness = total_minutes - start_minutes_1;
          brightness = brightness / duration_1 * max_brightness_1;
      } else if (total_minutes >= start_minutes_2 && total_minutes < end_minutes_2) {
         brightness = max_brightness_2;
      } else if (total_minutes >= start_minutes_3 && total_minutes < end_minutes_3) {
          brightness = total_minutes - start_minutes_3 + duration_1 + duration_2;
          brightness = (duration_1 + duration_2 + duration_3 - brightness) / duration_3 * max_brightness_2;
      }
      return brightness;

##### This bit is my guesswork. Doesn't compile###
        
   - platform: template
    id: bright
    max_value: 100
    min_value: 0
    step: 1
    optimistic: True
    on_value:
        then:
          - light.turn_on:
              id: led
              brightness: !lambda |-
              return id(number.brightness).state / 100.0; 

#######


text_sensor:
  - platform: template
    name: "Current time"
    lambda: |-
      char str[17];
      time_t currTime = id(sntp_time).now().timestamp;
      strftime(str, sizeof(str), "%Y-%m-%d %H:%M", localtime(&currTime));
      return  { str };
    update_interval: 30s

Thank you

Ok yes I see.

Probably a few ways you could do it.

Maybe try a copy number pointing at this first template number and then attach an on_value to that?

There’s probably a more concise way but I think that should work…

1 Like

Thanks, I got there in the end. And managed to also incorporate a physical switch that lets me choose whether I want it to run a schedule or just keep the light up at 100%. Issue with the 100% and my current code is that I can’t control it from HA anymore, as it bounces back to 100% due to on_value.

  name: esp-test

esp32:
  board: esp32dev


# Enable logging
logger:

# Enable Home Assistant API
api:


ota:


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.169
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.1
    dns2: 192.168.1.2
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp-Test Fallback Hotspot"
    password: "ZsDsw6T6JnAp"

captive_portal:


web_server:
  port: 80


time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/London

output:
  - platform: ledc
    id: led_output
    pin: GPIO16


light:
  - platform: monochromatic
    name: LED
    id: led
    output: led_output
    default_transition_length: 0.5s


number:
  - platform: template
    id: test_brightness
    icon: mdi:target-variant
    name: "Brightness"
    update_interval: 1s
    step: 1
    min_value: 0
    max_value: 100
    set_action:
       then:
        - delay: 0s
    on_value:
      - light.turn_on:
          id: led
          brightness: !lambda |-
                if (id(test_switch).state) {
                return x / 100.0;
                } else {
                return 1.0;
                }
              
    
    lambda: |-
      auto time_now = id(sntp_time).now();
      int total_minutes = time_now.second *60; //Comment this out after testing. It uses seconds so you can see changes quicker.
      //int total_minutes = time_now.hour * 60 + time_now.minute;
      float brightness = 0;
      int start_minutes_1 = 0 * 60;
      int end_minutes_1 = 10 * 60;
      int start_minutes_2 = 10 * 60;
      int end_minutes_2 = 30 * 60;
      int start_minutes_3 = 30 * 60;
      int end_minutes_3 = 60 * 60;
      int duration_1 = end_minutes_1 - start_minutes_1;
      int duration_2 = end_minutes_2 - start_minutes_2;
      int duration_3 = end_minutes_3 - start_minutes_3;
      float max_brightness_1 = 100.0;
      float max_brightness_2 = 100.0;
      if (total_minutes >= start_minutes_1 && total_minutes < end_minutes_1) {
          brightness = total_minutes - start_minutes_1;
          brightness = brightness / duration_1 * max_brightness_1;
      } else if (total_minutes >= start_minutes_2 && total_minutes < end_minutes_2) {
         brightness = max_brightness_2;
      } else if (total_minutes >= start_minutes_3 && total_minutes < end_minutes_3) {
          brightness = total_minutes - start_minutes_3 + duration_1 + duration_2;
          brightness = (duration_1 + duration_2 + duration_3 - brightness) / duration_3 * max_brightness_2;
      }
      return brightness;

text_sensor:
  - platform: template
    name: "Current time"
    lambda: |-
      char str[17];
      time_t currTime = id(sntp_time).now().timestamp;
      strftime(str, sizeof(str), "%Y-%m-%d %H:%M", localtime(&currTime));
      return  { str };
    update_interval: 30s

binary_sensor:
  - platform: gpio
    name: 'Test Switch'
    id: test_switch
    pin:
      number: GPIO17
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
        - delayed_on: 10ms
        - delayed_off: 10ms

Nice.

Yeah I have some similar manual/auto mode toggles.