ESP32 Touch Lamp with Dimming Capability

I have two lamps I’m looking to make ‘smart’, whilst retaining their existing features. They are touch lamps, and - each time they are touched - they cycle through different brightnesses (e.g. 25%, 50%, 75%, 100% and then back to 0%).

I want to recreate exactly this behaviour, whilst also incorporating them into my smart home. I’m close! But not quite there.

I’m using:

  1. ESP32-S3 mini ESP32-S3 Mini Development Board with Pre-soldered Header, Based on ESP32-S3FH4R2 Dual-Core Processor, 240MHz Running Frequency, Support 2.4GHz Wi-Fi (802.11 b/g/n) & Bluetooth 5 : Amazon.co.uk: Computers & Accessories
  2. RobotDyn AC Dimmer AC Dimmer Module, 1 Channel, 3.3V/5V logic, AC 50/60hz, 110V~400V - Robotdyn

I’ve borrowed bits of YAML from here: Howto make a smart touch lamp with esp32s3 and a relay

Through the web interface or HA, I have control over the dimmer/power, and this bit works perfectly. It’s also detecting when I touch the touch pin. This is amazing, as I don’t really know what I’m doing, I’ve had a lot of help from ChatGPT to get to this point.

Problem (TL;DR):

Touching the pin only serves to turn off the lamp, which is not what I want. I want it to cycle through five brightness percentages (0, 25, 50, 75, 100) and back to zero if it’s at 100.

One other requirement: If the brightness has been set to something other than a multiple of 25 via the web interface or HA, I want the next touch to set the brightness to the next number in the sequence (i.e. if it’s currently at 10%, I don’t want to add 25 and end up at 35, I want it to go to 25. Likewise if it’s at 26, I want the next touch to take it to 50, not 51).

Here’s my YAML:


esphome:
  name: "smart-touch-1"
  friendly_name: Smart Touch Lamp 1

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: arduino

# Enable logging
logger:
  level: info
#  level: debug

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_key

ota:
  - platform: esphome
    password: !secret ota_password

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "FR-Lamp-Touch"
    password: !secret wifi_password

# Enable Web server.
web_server:
  port: 80
  
captive_portal:

sensor:
  # hack so the touch sensor doesn't kick off at power on
  - platform: uptime
    name: Uptime Sensor
    id: time_since_boot
    update_interval: 3s
    internal: True
  - platform: template
    id: current_brightness_variable
    name: "Current Brightness"
    unit_of_measurement: "%"
    accuracy_decimals: 0
    state_class: "measurement"

text_sensor:
  - platform: template
    name: "smart touch lamp"
    id: smart_touch_lamp
    icon: "mdi:toggle-switch"
    on_value:
      then:
        - if:
            condition:
              text_sensor.state:
                id: smart_touch_lamp
                state: ""
            else:
              - delay: 200ms
              - text_sensor.template.publish:
                  id: smart_touch_lamp
                  state: !lambda 'return "";'


esp32_touch:
  setup_mode: false
  measurement_duration: 0.25ms
  
binary_sensor:
  - platform: esp32_touch
    name: "Touch Pad Pin 13"
    pin: GPIO13
    threshold: 550000
    filters:
      # Small filter, to debounce the spurious events.
      - delayed_on: 10ms
      - delayed_off: 10ms
    #on_press:
    on_click:
    - min_length: 10ms
      max_length: 500ms
      # Short touch to cycle through brightnesses
      then:
        - lambda: |-
            int current_brightness = id(dimmer).current_values.get_brightness() * 100;
            current_brightness += 25;
            if (current_brightness > 100) {
              current_brightness = 0;
            }
            // Store the brightness value for later use
            id(current_brightness_variable).publish_state(current_brightness);

        - light.turn_on:
            id: dimmer
            brightness: !lambda 'return id(current_brightness_variable).state / 100.0;'
        - logger.log: 
            level: info
            format: "Touch turn switch on"
 #       - if:
 #         # test to ignore random event on boot and when touch is disabled
 #           condition:
 #             - lambda: 'return  (id(time_since_boot).raw_state > 1 && (!(id(touch_disabled).state)));'
 #           then:
 #             if:
 #                 condition:
 #                     # if light is off
 #                     - switch.is_off: power_switch
 #                 then:
 #                   # then we turn it on
 #                   - switch.turn_on: power_switch
 #                   - logger.log: 
 #                       level: info
 #                       format: "Touch turn switch on"
 #                 else:
 #                   # else it's on so we turn it off
 #                   - switch.turn_off: power_switch
 #                   - logger.log: 
 #                       level: info
 #                       format: "Touch turn switch off"

    - min_length: 500ms
      max_length: 2000ms
      then:
        - logger.log: "${devicename} long click"
        - if:
          # test to ignore random event on boot and when touch is disabled
            condition:
              - lambda: 'return  (id(time_since_boot).raw_state > 1 && (!(id(touch_disabled).state)));'
            then:
              text_sensor.template.publish:
                id: smart_touch_lamp
                state: !lambda 'return "long_press";'
        

# restart-button
button:
  - platform: restart
    name: "restart-esp32-dim-touch"

# AC Dimmer Configuration
light:
  - platform: monochromatic
    id: dimmer
    name: "Smart Lamp"
    output: ac_dimmer_output
    restore_mode: ALWAYS_ON
    on_turn_on:
      - light.turn_on:
          id: dimmer
          brightness: 1.0  # Set initial brightness to 100% on power up

output:
  - platform: ac_dimmer
    id: ac_dimmer_output
    gate_pin: GPIO12  # Gate pin for controlling the dimmer (GPIO12)
    zero_cross_pin:
      number: GPIO5  # Zero-cross pin for synchronization (GPIO5)
      mode:
        input: True
      inverted: yes

switch:
 # - platform: gpio
 #   name: power_switch
 #   id: power_switch
 #   restore_mode: RESTORE_DEFAULT_OFF
 #   pin: GPIO7

  - platform: template
    name: "touch disabled"
    id: touch_disabled
    restore_mode: ALWAYS_OFF
    optimistic: true

I’m an ESPHome newbie, so please ELI5 if you’re able to kindly advise.


If you only need those 25% steps, you could simplify your code with template number:

  - platform: template
    name: Dimmer Control
    id: dimmer_control
    min_value: 0
    max_value: 100
    step: 25
    optimistic: true
    set_action:
      then:
        - light.turn_on:
            id: dimmer
            brightness: !lambda 'return x / 100.0;'

And update the number with your touch button:

- number.increment:
    id: dimmer_control
    cycle: true

that would increment 25% every press and start over after 100%

Thanks so much, and I’m so sorry to ask this stupid question, but what bit am I replacing with this?

Good question. I have no idea what half of your code is really serving…
If you want to try, replace everything from captive portal with following:
(not tested, I don’t have your hardware)

captive_portal:


esp32_touch:
  setup_mode: false
  measurement_duration: 0.25ms
  
binary_sensor:
  - platform: esp32_touch
    name: "Touch Pad Pin 13"
    pin: GPIO13
    threshold: 550000
    filters:
      # Small filter, to debounce the spurious events.
      - delayed_on: 10ms
      - delayed_off: 10ms
    #on_press:
    on_click:
    - min_length: 10ms
      max_length: 500ms
      # Short touch to cycle through brightnesses
      then:
        - number.increment:
            id: dimmer_control
            cycle: true
number:     
  - platform: template
    name: Dimmer Control
    id: dimmer_control
    min_value: 0
    max_value: 100
    step: 25
    optimistic: true
    set_action:
      then:
        - light.turn_on:
            id: dimmer
            brightness: !lambda 'return x / 100.0;'

# restart-button
button:
  - platform: restart
    name: "restart-esp32-dim-touch"

# AC Dimmer Configuration
light:
  - platform: monochromatic
    id: dimmer
    name: "Smart Lamp"
    output: ac_dimmer_output
    restore_mode: ALWAYS_ON
    #on_turn_on:  IS THIS NEEDED??
    #  - light.turn_on:
    #      id: dimmer
    #      brightness: 1.0  # Set initial brightness to 100% on power up

output:
  - platform: ac_dimmer
    id: ac_dimmer_output
    gate_pin: GPIO12  # Gate pin for controlling the dimmer (GPIO12)
    zero_cross_pin:
      number: GPIO5  # Zero-cross pin for synchronization (GPIO5)
      mode:
        input: True
      inverted: yes


Extremely kind of you, thank you. I will test this out

Worked like a charm! Thank you so much!

You’re welcome!
By the way, I do have robotdyn dimmer waiting for to be used (not with esphome) for a quite big load. How long have you been using it? No problems?

Sorry for the delay! This is the first time I’m trying one. Unfortunately, I’m experiencing some flickering when the dimmer is anything other than 100% brightness, your mileage may vary!

I think it’s because of esphome and the ac-dimmer component. That’s the reason I decided not to run it on esphome in the first place.
Zero crossing detection is not suitable for a setup that is busy with one million other things.