Simple Automation to Cycle Through Turning a Switch On and Off Regularly

I thought this would be a simple automation. I want to turn a smart switch on and off for set a duration. Say, one hour off and a half-hour on. This would repeat or loop indefinitely as long as the automation is enabled.

I thought I had it figured out; two automations. Each would have a state trigger with a to: 'off' or to: 'on'. It would also have a for: '1:00:00' or for: '0:30:00' to establish the duration. A state change either way would trigger one or the other, but only after waiting the for: duration.

Sometimes it works, sometimes the automations toggle the switch twice or three times.

Backstory:
I bought one of those plug-in air fresheners. It’s overpowering! I had a couple of Sengled smart plugs I’ve been testing and figured I’d use one of those and let HA cycle the air freshener on and off to lower the intensity. Obviously this isn’t critical. But I’d like to learn why it’s not working.

Here’s an example of toggling the switch three times:

And here’s the automations config:

id: id_air_freshener_on
alias: Air Freshener On
description: Air Freshener On
trigger:
  - platform: state
    entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
    for: '1:00:00'
    to: 'off'
condition: []
action:
  - service: switch.turn_on
    target:
      entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
mode: single
id: id_air_freshener_on
alias: Air Freshener On
description: Air Freshener On
trigger:
  - platform: state
    entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
    for: '1:00:00'
    to: 'off'
condition: []
action:
  - service: switch.turn_on
    target:
      entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
  - delay: '00:30:00'
  - service: switch.turn_off
    target:
      entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
mode: single

You forgot to define, how long the switch must stay on :wink:

You’re over thinking this, 1 automation to rule them all.

id: id_air_freshener
alias: Air Freshener
description: Air Freshener
trigger:
  - platform: time_pattern
    minutes: 30
action:
  - service: switch.toggle
    target:
      entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
mode: single
3 Likes

Well, I tried to stay with the initial question, 1 hour off, 30 minutes on :smiley: and using the logic, OP started with.

yah, that simple automation won’t work for offsets like that

Maybe I’m missing something. I want this automation to turn the switch on after it’s been off for one hour, and leave it that way.

There’s a second automation which is supposed to turn the switch off after its been on for one half-hour. It’s identical except swap “off” and “on,” and change 1:00:00 to “0:30:00”

They both run when expected, except they sometimes turn the switch on/off repeatedly, as shown in the OP, instead of just turning it on (or off) once, as I expected.

I do like the solution @petro posted. Much simpler. But it forces me to keep it on 50% of the time. I could probably live with that, but now that I’ve bumped into this problem where the action takes place two or three times, I’m curious what’s going on.

Your words…

I’m still getting multiple triggers in my automation.

Maybe I need to explain the problem better. My automation calls the switch.turn_on service, but when it’s triggered, the switch is sometimes turned on, then immediately off. Sometimes it’s turned on, then off, then back on. Other times it’s only turned on once, as expected.

Likewise, an almost identical automation calls the switch.turn_off service. Again, it sometimes turns the switch off then on, or off then on then off.

My question is, what is causing one automation to toggle the switch more than once, instead of just turning it on or off once (depending on which service I call)?

Final follow-up:

I gave up on my original code. I tried deleting and re-adding the automation. Changed the duration. Stopped and restarted HA. Even updated the HA version. I’d still get apparently random cases when the automation would get triggered once, but the action wold run two or three times.

For my purposes, a half-hour on and a half-hour off works OK, so I’m going with the solution @petro posted (thank you!!)

I confirmed that minutes: 30 means “on the hour and half-hour,” not “every thirty minutes.” The documentation says you can use minutes: '/30' and it will run every 30 minutes, but when I did that it’s still triggering on the hour and half-hour.

Some day I’ll come back to this, and if I can’t solve it myself I’ll define the problem better in my thread title.

It’s a common misconception that using /5 implies every 5 minutes, starting from the moment the automation is created/started. That’s false.

If the current hour is 13 then it will trigger at 13:05, 13:10, 13:15, etc. If the automation is created at 13:02, it will trigger at 13:05 (not 13:07).

1 Like

Yes, I discovered that, thank you for confirming it’s not just me.

For the record, this is what the documentation says:

automation:
  trigger:
    - platform: time_pattern
      # Matches every hour at 5 minutes past whole
      minutes: 5

automation 3:
  trigger:
    - platform: time_pattern
      **# You can also match on interval. This will match every 5 minutes**
      minutes: **"/5**"

Maybe someone can explain what that really means?

The first will trigger only once per hour at five minutes past the hour. For example 13:05, 14:05, 15:05…

The second triggers every 5 minute interval of each hour. So 13:05, 13:10, 13:15, 13:20…

1 Like

Here’s the line of code that’s responsible for computing a Time Pattern’s repeating interval:

When you use this:

    - platform: time_pattern
      minutes: '/5'

that line of python code (line 223) does this with it:

[x for x in range(0, 59 + 1) if x % 5 == 0]

which produces this:

[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]

That list contains the minutes in an hour when the Time Pattern Trigger will trigger. Basically, it uses the 5 minutes you supplied to calculate 5-minute intervals in an hour (starting at the beginning of the hour).

To gain a better understanding of what this means, I used python3 to produce results for 5, 10, 15, 20, 25, 30, 35, 40, and 45 minutes.

The takeaway here is that when using /30 or greater, it will only trigger twice in an hour:

  1. On the hour (0 minutes).
  2. At the interval past the hour (30 minutes).

Hope this helps to clarify how it works.

1 Like

I don’t have an answer as to why you are getting multiple toggles.

There’s probably a more elegant way to do it, but the following should give you the 30 min on/60 mins off effect you were trying for…

alias: Asymmetrical Timed Light 
description: ''
trigger:
  - platform: time_pattern
    id: Increment Input_number
    minutes: /30
  - platform: state
    entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
    id: Turn Off
    to: 'on'
    for:
      hours: 0
      minutes: 30
      seconds: 0
      milliseconds: 0
  - platform: template
    value_template: >-
      {{ true if (states('input_number.test_number')|int) % 3 ==
      0 else false }}
    id: Turn On
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id: Turn On
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
      - conditions:
          - condition: trigger
            id: Turn Off
        sequence:
          - service: switch.turn_off
            target:
              entity_id: switch.sengled_e1c_nb7_e08a6b03_on_off
      - conditions:
          - condition: trigger
            id: Increment Input_number
        sequence:
          - choose:
              - conditions:
                  - condition: numeric_state
                    entity_id: input_number.test_number
                    above: '2'
                sequence:
                  - service: input_number.set_value
                    target:
                      entity_id: input_number.test_number
                    data:
                      value: 1
            default:
              - service: input_number.increment
                target:
                  entity_id: input_number.test_number
    default: []
mode: parallel
max: 3

I used an input number with the following configuration

initial: 1
min: 1
max: 3
step: 1
mode: box
1 Like

Try this version.

- alias: The Toggler
  description: 'ON for 30 minutes, OFF for 60 minutes'
  id: the_toggler
  trigger:
  - platform: time_pattern
    minutes: '/30'
  action:
  - variables:
      sw: 'switch.sengled_e1c_nb7_e08a6b03_on_off'
      sw_is_on: "{{ is_state(sw, 'on') }}"
      time_on: '{{ now() - states[sw].last_changed >= timedelta(minutes=29) }}'
      time_off: '{{ now() - states[sw].last_changed >= timedelta(minutes=59) }}'
  - choose:
    - conditions: '{{ sw_is_on and time_on }}'
      sequence:
      - service: switch.turn_off
        target:
          entity_id: '{{ sw }}'
    - conditions: '{{ not sw_is_on and time_off }}'
      sequence:
      - service: switch.turn_on
        target:
          entity_id: '{{ sw }}'

I tested it with a shorter cycle and confirmed it works.

During testing, the stumbling block I encountered was the value used in the timedelta. It needs to be just under the desired time period in order for the time comparison to work properly.

So if the desired period is in minutes, make the value in timedelta one or two minutes less than the desired time period (if in seconds then reduce by a second or two). You wanted a period of 30 minutes ON and 60 minutes OFF so the values are 29 and 59, respectively.

Reminder: this Time Pattern Trigger is using /30 minutes so it will trigger on the hour (like 12:00) and the half-hour (12:30).

2 Likes

Thank you for this, it saved me from headache. After trying to get along with two separate automation rules for on and off, this seems to work perfectly.

I modified it to run every 1 minute and check if time has come to toggle the device. The duty cycle is controlled by a slider (from 0 to 100). I also added a variable to set full length of the cycle.

Here is my version:

- id: the_toggler
  alias: Boiler Slow PWM
  trigger:
  - platform: time_pattern
    minutes: /1
  action:
  - variables:
      sw: 'switch.tasmota_1'
      full_cycle_s: 1500
      seconds_on: "{{ full_cycle_s * (states('input_number.boiler_duty')|int) // 100 }}"
      seconds_off: "{{ full_cycle_s * (100-states('input_number.boiler_duty')|int) // 100 }}"
  - choose:
    - conditions: "{{ is_state(sw, 'on') and now() - states[sw].last_changed >= timedelta(seconds=seconds_on) }}"
      sequence:
      - service: switch.turn_off
        target:
          entity_id: '{{ sw }}'
    - conditions: "{{ is_state(sw, 'off') and now() - states[sw].last_changed >= timedelta(seconds=seconds_off) }}"
      sequence:
      - service: switch.turn_on
        target:
          entity_id: '{{ sw }}'
1 Like