Home AssistantESPHome

Button scene cycling

One button cycles a list of scenes on short press (a counter remembers the step), resets after a timeout, and a long press — or pressing past the last scene — turns the room off.

Difficulty · BeginnerSetup · 20–40 minutesHA · current stable (blueprint)ESPHome · current stable
Get the pack · from $19Have us set it up

Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.

A pile of scene buttons on one remote is how you get the wrong purple. This workflow uses one button: each short press advances a counter and turns on the next scene in a list you order. Sit idle past a timeout and the counter goes back to zero so the next evening starts on the first scene again. A long press — or one more short press after the last scene — runs homeassistant.turn_off on the targets and resets the counter.

Entity ids are blueprint inputs. Do not publish a live room’s scene names.

What problem it solves

You want a single hallway or nightstand button that walks “dim → reading → bright” and then off, without a second “off” paddle if the hardware does not have one, and without leaving the counter stuck on scene three tomorrow.

How it works

  1. Short press — increment counter.scene_step (step size 1). If the new value is still inside the scene list, scene.turn_on that index (1-based). The first press after a reset always hits the first scene.
  2. Past the last scene — if the counter would exceed the list length, skip the scene call, turn the target lights/switches off, and counter.reset. That last extra tap is the off command on a single-button remote.
  3. Long press — same off + reset path, immediately. Use this when the device exposes long / hold (ESPHome on_multi_click, Zigbee hold, Hue-class event entities). If the button is short-only, the wrap-around off is enough.
  4. Idle timeout — after the last press, wait the idle window (pack default a few minutes). If nobody presses again, reset the counter without changing lights. The room stays as it is; the next short press starts at scene one instead of “whatever we left.”
  5. Restart-safe step — the counter helper is the memory. An automation variable dies on reboot; the counter does not, so a press after a HA restart continues from the stored step (until idle timeout fires).

Put the scene you want first in the list. Off is not a scene; it is the wrap / long-press path.

Hardware notes

  • ESPHome GPIO. A momentary pushbutton to ground with pullup and on_multi_click (short vs long). Any small ESP32 or an existing node with a spare pin.
  • Zigbee / Z-Wave remote. A device that publishes short and (ideally) long events. One button is enough; a separate off button can call the same off script if you have one.
  • Wall switch. A smart switch in detached / event mode, or a scene paddle. Do not use a switch that cuts mains to the lamps you are dimming if the bulbs need constant power.
  • Scenes. HA scene entities. Keep them in one area so “off” has a clear target list (the same lights the scenes own).

Snippet

The pack ships a blueprint. Inputs replace real entity ids:

trigger:
  - platform: state
    entity_id: !input button_short
    to: "on"
    id: short
  - platform: state
    entity_id: !input button_long
    to: "on"
    id: long
  - platform: state
    entity_id: !input scene_counter
    id: stepped
    # idle reset uses a wait / timer in the full blueprint
action:
  - choose:
      - conditions:
          - condition: trigger
            id: short
        sequence:
          - service: counter.increment
            target:
              entity_id: !input scene_counter
          - variables:
              step: "{{ states(scene_counter) | int(0) }}"
              scenes: !input scene_list
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ step <= scenes | count }}"
                sequence:
                  - service: scene.turn_on
                    target:
                      entity_id: "{{ scenes[step - 1] }}"
            default:
              - service: homeassistant.turn_off
                target:
                  entity_id: !input off_targets
              - service: counter.reset
                target:
                  entity_id: !input scene_counter
      - conditions:
          - condition: trigger
            id: long
        sequence:
          - service: homeassistant.turn_off
            target:
              entity_id: !input off_targets
          - service: counter.reset
            target:
              entity_id: !input scene_counter

Optional ESPHome button (credentials stay in secrets.yaml):

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: true
    name: "Scene button"
    on_multi_click:
      - timing:
          - ON for at most 400ms
        then:
          - homeassistant.event:
              event: esphome.scene_button
              data:
                click: short
      - timing:
          - ON for at least 800ms
        then:
          - homeassistant.event:
              event: esphome.scene_button
              data:
                click: long

Setup outline

  1. Create the scenes in the order you want to walk. Create counter.scene_step (minimum 0, step 1).
  2. Confirm the button’s short (and long, if any) entities in Developer Tools.
  3. Import the blueprint. Fill button entities, scene list, off targets, counter, idle timeout.
  4. Test: short, short, short → scenes 1, 2, 3; one more short → off and counter 0; long press from any step → off; wait the idle timeout after scene 2 → next short is scene 1.

Troubleshooting

  • Skips a scene. The counter step is not 1, or you incremented twice (two automations on the same button). One blueprint instance per button.
  • Never reaches off. The wrap check uses a different list length than the scenes you selected, or long-press is wired to the short entity. Count the list; fire long from Developer Tools.
  • Next day starts on the last scene. Idle timeout never ran (automation disabled, or timeout longer than you think). Lower it, or reset the counter from the off path only and accept that a reboot mid-evening keeps the step — that is the restart-safe part.
  • GPIO short and long both fire. on_multi_click timings overlap. Shorten the short window and lengthen the long ON for at least so they cannot both match.

Credits

Based on the community blueprint Button Scene Cycling and Off Control. This write-up is an original overview of a one-button counter cycle with idle reset and wrap-to-off.

Changelog

v1.0 · Oct 17, 2026: First publish.