Push button dimmer - how?

Thanks for this code, friend! This is exactly what I was looking for my nightlights with one touch button. Before this code, I only turned them on and off with a button, and set the brightness in HA

I tryed your node-red code for dimming a light when holding button and I couldn’t get it to work.
Recently I made a 4 momentary buttons using a NodeMCU running Tasmota to control my light and switches. But I would like to expand the possibilities, like double press, triple press and hold doing other tasks. If possible, could you kindly post the nodes you use to achieve this? I can help you with esp8266 configuration if you need any help. Thanks.

Yes, the dimming code is just an idea.
Did not test it in real life, also I have not added any dimming nodes. just that there is a flow to the dimming part.
You will have to add more nodes to do the dimming.
Im not sure if its enough with a call to dim, or if you need to make a looping function that will call the dimming with incremental steps.
You would probably have to check when the button is released to stop the loop.

The multi-press part I can help you with, but Im not home at the moment, but from memory, what I did was having a trigger node that looks at the sensor state (button press) and after that I installed a counter node (you have to search for counter in the repository or what its called :slight_smile: )
In the counter node, you have to set a timeout (think I went with 500ms), that will reset for each incoming messages. Once the timeout is reached a message is sent out from the counter node with a count number.

This message goes into a switch node, where you add multiple outputs depending on how many presses you like to handle.

So add like count == 1, 2, 3 etc in the switch node. You can just go with count == 2, 3 and then an else for “single” press. This will also failover to single press if you press too many times. This is up to you :slight_smile:

Hope this will help.

Thanks for your tips.
I searched for counter but it only returned one node (node-red-contrib-counter), this one was already installed in my Node-Red (HASSIO). It also doesn’t have a timeout option, which makes me think that the node may have another name.

The correct is node-red-contrib-timed-counter “A node that counts messages received in a specified time limit.”
This one has the timeout which I think is the one in use in my flows.

This is my solution. Short push on, off. Long press increases brightness and decreases brightness. It works on the inverter. Light on remembers last value of brightness. There is a extra output of brightness in logs esphome for debuging. It works on Wemos D1 mini. D2 pin is for push button and D4 for mosfet irlb8721 with LED 8mm 150mA. All this for a 3D printed decorative table lamp.

globals:
  - id: my_global_bool
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: my_global_float
    type: float
    restore_value: no
    initial_value: '0.3'
    
switch:
# This is to restart the ESPHome device remotely
  - platform: restart
    name: "Restart ESPHome - Kuchyna_Dimmer"


sensor:
  - platform: wifi_signal
    name: "WiFi - Kuchyna_Dimmer"
    update_interval: 60s


output:
  - platform: esp8266_pwm
    pin: D4
    frequency: 1000 Hz
    id: pwm_a
    #inverted: TRUE

light:
  - platform: monochromatic
    name: "kuchyna_Dimmer"
    id: led
    output: pwm_a
    gamma_correct: 0

binary_sensor:
  - platform: gpio
    pin:
      number: 4
      inverted: TRUE
      mode: INPUT_PULLUP
    id: led_touch
    filters:
      - delayed_on: 50ms  
      - delayed_off: 50ms 

    on_click:
      then:
        - if:
            condition:
              light.is_off: led
            then:
              - light.turn_on: 
                  id: led
                  brightness: !lambda |-
                    return id(my_global_float);
              - lambda: |-
                  id(my_global_bool) = (true);
                  ESP_LOGD("main", "Global value brightness is: %f", id(my_global_float));
            else:
              light.turn_off: led
             
    on_press:
      then:
      - if:
          condition: 
              lambda: |-
                return id(my_global_bool);
# When above condition evaluates to true - Fade_up function else Fade_down
          then:
          - delay: 0.5s
          - while:
              condition:
                binary_sensor.is_on: led_touch
              then:
                - light.dim_relative:
                    id: led
                    relative_brightness: 2%
                    transition_length: 0.1s
                - delay: 0.1s
                - lambda: |-
                    id(my_global_float) = id(led).current_values.get_brightness();
                    ESP_LOGD("main", "Global value is: %f", id(my_global_float));
          - lambda: |-
              id(my_global_bool) = (false);
          else:
          - delay: 0.5s
          - while:
              condition:
                and:
                  - binary_sensor.is_on: led_touch
# This is where I want to set minimum value so that touch sensor only allows 4% minimum
                  - lambda: |-
                      return {id(led).current_values.get_brightness() > 0.04};
              then:
                - light.dim_relative:
                    id: led
                    relative_brightness: -2%
                    transition_length: 0.1s
                - delay: 0.1s
                - lambda: |-
                    id(my_global_float) = id(led).current_values.get_brightness();
                    ESP_LOGD("main", "Global value brightness is: %f", id(my_global_float));
          - lambda: |-
              id(my_global_bool) = (true);
6 Likes

@drdolitle1 you mean MOSFET - IRLB8721.
Can you please give some schematics? D2 is push button connected to GND pin. D4 (PWM output) goes directly to Gate input of the MOSFET. Source of the MOSFET is connected to the GND and Drain of the MOSFET is connected to the - of the LEDs? + of the LEDs is connected to 12/24V of the power supply for LEDs? Is this correct?

Did you want to automate this within ESPHome (so the button behaviour works whether HA is connected or not), or do you not mind it being dependent on HA?

If the former, I’m thinking this could be implemented using Automation Lambda’s, and one global variable.
As I understand what you want it to do:

  • Respond to short-press by changing directly to a new state (i.e. on/off).
  • Respond to long-press by incrementally dimming up/down at some rate, until the button is released or a limit is reached.

If that’s what you want, you may be able to use ESPhome’s ‘interval’ sensor, and on-press automations to set a static (ESPHome calls them ‘global’) variable that all Lambda’s can reference.

You can implement a simple ‘state machine’ using a global variable to track the time of button-down events, but don’t otherwise act on button-down events.
The interval timer will fire at its interval (whether a button is down or not) and its lambda will check if a button is down now, and if so, will query the global variable to see if the button has been down for at least 1 second. We’ll return to this logic in a moment. But first, what do you do when the button is released.

When a button is released, have the button-up lambda check your variable (the millisecond count when the button was pressed) to see if a second has elapsed. If less than a second, act on the button now (on/off). This handles the momentary-press case.

Now to the case where you hold the button down, and this logic takes over:
When the interval triggers, it should first check to see if a button is down, and also if at least one second has elapsed.
If both are true, then incrementally dim/brighten the light (in the direction of whichever button it is that’s down).
That interval will fire repeatedly, noticing that the button is still down, that 1 second has still elapsed since press, and it will keep on incrementally dimming/brightening as long as those things remain true.

When the button finally is released, the dimming/brightening stops happening, because the interval tests will fail, and the change is done. (Oh, and this long-press button-release won’t cause immediate full dim/brighten described earlier, because it acts only if the press was less than a second long.)

Just set the interval to something like 1/4 second, so it doesn’t dim/brighten so slowly at glacial 1 second intervals - you’ll want those to clock along a bit faster.

And, in case it helps, here is a portion of a config attempting to implement the above.
I hope this accomplishes what you’re looking for, and without making HA do any of the work - it all happens inside the ESP.
This was written for the onboard blue LED and a couple GPIO pins with pushbuttons wired to ground.

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO4
      mode:
        input: true
        pullup: true
      inverted: true
    name: "On/Brighten"
    id: on_bright
    on_press:
      then:
        - lambda: |-
            id(when_button_pressed) = millis();
    on_release:
      then:
        - if:
            condition:
            - lambda: 'return (millis() - id(when_button_pressed)) < 1000;'
            then:
              light.turn_on: the_blue_led
  - platform: gpio
    pin:
      number: GPIO5
      mode:
        input: true
        pullup: true
      inverted: true
    name: "Off/Dim"
    id: off_dim
    on_press:
      then:
        - lambda: |-
            id(when_button_pressed) = millis();
    on_release:
      then:
        - if:
            condition:
            - lambda: 'return (millis() - id(when_button_pressed)) < 1000;'
            then:
              light.turn_off: the_blue_led

globals:
  - id: when_button_pressed
    type: int
    restore_value: no

interval:
  - interval: 0.5s
    then:
      - if:
          condition:
            - and:
                - binary_sensor.is_on: off_dim
                - lambda: 'return (millis()-id(when_button_pressed)) >= 1000;'
          then:
            - logger.log: "dimming..."
            - light.dim_relative:
                id: the_blue_led
                relative_brightness: -5%
      - if:
          condition:
            - and:
                - binary_sensor.is_on: on_bright
                - lambda: 'return (millis()-id(when_button_pressed)) >= 1000;'
          then:
            - logger.log: "brightening..."
            - light.dim_relative:
                id: the_blue_led
                relative_brightness: 5%

light:
- platform: monochromatic
  name: "${node_name} Blue LED"
  output: blue_led
  id: the_blue_led
output:
- platform: esp8266_pwm
  id: blue_led
  pin: GPIO2
  inverted: True
2 Likes

Not sure if this helps, but I use this for my moeshouse dimmers:

esphome:
  name: qs-wifi-ds01-ip52
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret wifi_ssid1
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "qs-wifi-ds01-ip52 Fallback"
    password: "fallback"

captive_portal:

# Enable Home Assistant API
api:
  password: !secret api_password

ota:

# Enable logging
logger:
  baud_rate: 0
  level: DEBUG
  logs:
    sensor: ERROR
    duty_cycle: ERROR
    binary_sensor: ERROR
    light: ERROR

# Example configuration entry (optional web server)
web_server:
  port: 80

time:
  - platform: homeassistant

substitutions:
  switch_id: "dim_01"

# globals:
# Dummy light brightness tracker Global

globals:
  # Dim direction for Switch 1: 0=Up (brighten) 1=down (dim)
  - id: g_direction_1
    type: int
    restore_value: no
    initial_value: "1"
  # Counter for time pressed for switch 1
  - id: g_counter_1
    type: int
    restore_value: no
    initial_value: "0"
  # initial brightness

# Uart definition to talk to MCU dimmer
uart:
  tx_pin: GPIO1
  rx_pin: GPIO3
  stop_bits: 1
  baud_rate: 9600

sensor:
  - platform: wifi_signal
    name: "${switch_id} WiFi Signal Sensor"
    update_interval: 60s
  - platform: uptime
    name: "Uptime"
  # Primary template sensor to track Brightness of light object for "on_value" sending to MCU dimmer
  - platform: template
    name: "${switch_id} Brightness Sensor"
    id: sensor_g_bright
    internal: true
    update_interval: 20ms
    # Ensure on_value only triggered when brightness (0-255) changes
    filters:
      delta: 0.8
    # Read brightness (0 - 1) from light , convert to (0-255) for MCU
    lambda: |-
      if (id(light_main).remote_values.is_on()) {
        return (int(id(light_main).remote_values.get_brightness() * 255));
      }
      else {
        return 0;
      }
    # On Change send to MCU via UART
    on_value:
      then:
        - uart.write: !lambda |-
            return {0xFF, 0x55, (char) id(sensor_g_bright).state, 0x05, 0xDC, 0x0A};
        - logger.log:
            level: INFO
            format: "Sensor Value Change sent to UART %3.1f"
            args: ["id(sensor_g_bright).state"]
  # Sensor to detect button push (via duty_cycle of 50hz mains signal)
  - platform: duty_cycle
    pin: GPIO13
    internal: true
    id: sensor_push_switch
    name: "${switch_id} Sensor Push Switch"
    update_interval: 20ms

binary_sensor:
  #Binary sensor (on/off) which reads duty_cyle sensor readings.
  - platform: template
    id: switch1
    internal: true
    name: "${switch_id} Switch Binary Sensor"
    # read duty_cycle, convert to on/off
    lambda: |-
      if (id(sensor_push_switch).state < 95.0) {
        return true;
      } else {
        return false;
      }
    # Short Click - toggle light only
    on_click:
      max_length: 300ms
      then:
        light.toggle: light_main
    # Generic On_Press - log press, toggle DIM Direction and reset press interval counter
    on_press:
      then:
        - logger.log: "Switch 1 Press"
        - lambda: |-
            if (id(g_direction_1) == 0) {
              id(g_direction_1) = 1;
            } else {
              id(g_direction_1) = 0;
            }
            id(g_counter_1) = 0;

# Dummy light output to allow creation of light object
output:
  - platform: esp8266_pwm
    pin: GPIO14
    frequency: 800 Hz
    id: dummy_pwm

# Primary Light object exposed to HA
light:
  - platform: monochromatic
    default_transition_length: 20ms
    restore_mode: RESTORE_DEFAULT_OFF
    name: "${switch_id} Light"
    output: dummy_pwm
    id: light_main

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

# Polling object for long press handling of switch for dim/brighten cycle
interval:
  - interval: 20ms
    then:
      - if:
          condition:
            binary_sensor.is_on: switch1
          then:
            # Ramp rate for dim is product of interval (20ms) * number of intervals
            # Every 20ms Dimmer is increased/decreased by 2/255
            # Lower limit = 10%
            # Upper limit = 100%
            # 100% - 10% = 90% = 230/255. Therefore 230/2 * 20ms = 2.3 seconds for full range
            # At full/min brightness - further 16x20ms = 0.32 Seconds "dwell" by resetting counter to 0
            # Initial pause for 16x20ms = 0.32s to allow "on_click" to be discounted 1st
            # g_direction_1 = 0 (Increasing brightness)
            # g_direction_1 = 1 (decreasing brightness)
            # g_counter_1 = Interval pulse counter

            lambda: |-
              float curr_bright = id(light_main).remote_values.get_brightness();
              id(g_counter_1) += 1; 

              // If max bright, change direction
              if (curr_bright >= 0.999 && id(g_direction_1) == 0) {
                id(g_direction_1) = 1;
                id(g_counter_1) = 0;
              }

              // If below min_bright, change direction
              if (curr_bright < 0.1 && id(g_direction_1) == 1) {
                id(g_direction_1) = 0;
                id(g_counter_1) = 0;
              }

              if (id(g_direction_1) == 0 && id(g_counter_1) > 15) {
                // Increase Bright
                auto call = id(light_main).turn_on();
                call.set_brightness(curr_bright + (2.0/255.0));
                call.perform();
              }

              else if(id(g_direction_1) == 1 && id(g_counter_1) > 15) {
                // Decrease Bright
                auto call = id(light_main).turn_on();
                call.set_brightness(curr_bright - (2.0/255.0));
                call.perform();
              }

2 Likes

Exactly as you write with D2 and D4 pins.
In fact, I ended up using 3 LEDs.
8mm, 70mA at 3.2v. I couldn’t draw DC voltage regulator to 3.3V in schematic. I did not use LM317, but simple little DC regulator from aliexpress. I use 5V phone charger with usb cable. The cable is soldered to the VIN and GND pin at the other end.

Sorry for my english is not good.

circuit

y

You are absolutely right. The intention was to use a lamp primarily without HA. You also described exactly how the button should work. My project is relatively young and I haven’t even tried to see if it actually works without HA. I will try it. Also thank you for another post with code.

I can confirm that the lamp works without HA. I put the MAC address in the blacklist on the wifi routers. HA reports no connection. But, switching on/off the lamp as well as brightness still works with long press push button. After connecting to HA, I can still control the switching on and off of the lamp, as well as brightness with a slider in HA.
That’s exactly what I wanted to achieve.
The lamp is designed for decorative lighting and works well with 3pcs LEDs.

2 Likes

@drdolitle1 thanks for you feedback. I was testing also with my Wemos D1 mini and the code is working both with HA and on device itself. I didn’t manage to connect MOSFET (PWM driver) to the output to control my LED strip but I can see that the blue LED on Wemos PCB is dimming…

You need logic-level Mosfet with VGS(th) max 3.3V. IRLB8721 has full saturation on 2.35V. You probably know that. Just to be sure.

Thank you, @drdolitle1 - this works perfectly for my needs and improves my attempt no end!

I have it working on an ESP8266-01, using pin 2 for output to a MOSFET (through a opto-coupler) and pin 0 for a touch switch input.

Thanks again!

1 Like

Thanks for the code!

I added a few features I like:

  • Dim up and down constantly if max/min brightness is reached
  • Start with a default brightness of 70%
  • Always dim up after turning on (from off)
globals:
  - id: my_global_bool
    type: bool
  - id: my_global_float
    type: float
    
binary_sensor:
  - platform: gpio
    pin:
      number: 34
      mode: INPUT    
      inverted: True
    filters:
      - delayed_on: 20ms
    name: "light switch with a name"
    id: "kids1_tast"
      
    on_click:
      then:
        - if:
            condition:
              light.is_off: light1
            then:
              - lambda: |-
                  id(my_global_float) = 0.7;
                  ESP_LOGD("main", "Global value brightness is: %f", id(my_global_float));
                  id(my_global_bool) = (true);  // make sure always dim up after turn on
              - light.turn_on: 
                  id: light1
                  brightness: !lambda |-
                    return id(my_global_float);        
            else:
              - light.turn_off: light1
       
    on_press:
      then:
        - delay: 0.5s
        - if:
            condition: 
              binary_sensor.is_on: kids1_tast
              # don't do a thing if not pressed long enough
            then:
              - while:
                  condition:
                    binary_sensor.is_on: kids1_tast
                  then:
                    - if:
                        condition: 
                            lambda: |-
                              return id(my_global_bool);
                        then:
                                
                          - light.dim_relative:
                              id: light1
                              relative_brightness: 2%
                              transition_length: 0.1s
                          - delay: 0.1s
                          - lambda: |-
                              id(my_global_float) = id(light1).current_values.get_brightness();
                              ESP_LOGD("main", "+Global value is: %f", id(my_global_float));
                              // invert dim direction if full on
                              if (id(light1).current_values.get_brightness() >= 0.99) id(my_global_bool) = false;
                        else:
                          - light.dim_relative:
                              id: light1
                              relative_brightness: -2%
                              transition_length: 0.1s
                          - delay: 0.1s
                          - lambda: |-
                              id(my_global_float) = id(light1).current_values.get_brightness();
                              ESP_LOGD("main", "Global value brightness is: %f", id(my_global_float)); 
                              if (id(light1).current_values.get_brightness() <= 0.04) id(my_global_bool) = true;                 
              - lambda: |-
                  id(my_global_bool) = !id(my_global_bool) ; // invert bool at end of while

cheers

5 Likes

Hi. I’m sorry, what platform do you use? I read this thread twice: @drdolitle1 is using Wemos, @aceindy went with Moes, does it work with any ESP8266 switch or does need an actual dimmer? For example, will the code work with Sonoff mini R2 and Dual R3 after certain adaptation?

will work. works for me on sonoff basic

You can see my solution Here

1 Like