Calling "Cleanup" type jobs from both HA startup and also on a schedule -?

Question on how to elegantly handle cleanup in the background from different kinds of triggers - (if that is the right way to say it) -

Scenario/Use case:
I have several automations that are called in parallel (with HA startup as the trigger) which do some ‘cleanup’ type of work. For instance there are imperfect or buggy integrations where when a call to turn something on or off fails because the connection is temporarily lost. Due to the complexity of all of the third party HA integrations this is bound to happen. For instance a timer that when it starts that is supposed to turn a light on and when finished, turn a light off. In that case because a failed call to turn the light off, the timer might not be running while the light is left on for example… So on startup I have a automations that if that light is on but the related timer is inactive (and I have that automation set as enabled), then either start the timer or turn off the light, etc.

What I would like to do is to call all of those “cleanup” automations once an hour or so, in addition to when HA starts up. So I have set up a schedule - but how to call all of those automations from the schedule? Or, should they all be changed into scripts which can then all be called from both HA startup automations and from automations triggered by schedule?

(I hate programming around other people’s bugs… It could be the most elegant way is to on a schedule to re-“call” such failed calls (for turning the light off until the light is actually off - if still applicable later), but that may be outside the scope of this discussion…)

I could make this work by duplicating the logic everywhere but just trying not to endlessly do that. Essentially I’d like to call these ‘cleanup jobs’ (for lack of a better term) from several places - thoughts?

Basically, just have two triggers to this type of automation - one that “fires” when HA starts, and the other that “fires” periodically. E.g.,

- alias: Cleanup xyz at startup and every hour
  trigger:
    - platform: homeassistant
      event: start
    - platform: time_pattern
      hours: "/1"
  action:
    - ...

Whether you put the steps directly in the automation, or into a script the automation calls, doesn’t really matter.

Another implementation might use an intermediate event. E.g.,

- alias: Run at startup and every hour
  trigger:
    - platform: homeassistant
      event: start
    - platform: time_pattern
      hours: "/1"
  action:
    - event: cleanup_time

Then in your automations:

- alias: Cleanup xyz
  trigger:
    - platform: event:
      event_type: cleanup_time
  action:
    - ...

Then if you want to change what causes the cleanup actions to run, you’ll have just one place to change that.

As far as retrying turning switches on or off when they fail, I’ve definitely had that issue with some Z-Wave devices, so I use this script to retry as necessary:

script:
  change_switches:
    alias: Change Switches
    mode: queued
    sequence:
      - repeat:
          while: >
            {{ states.switch | selectattr('entity_id', 'in', switches)
               | selectattr('state', 'ne', new_state) | list | length > 0 and
               repeat.index <= 3 }}
          sequence:
            - variables:
                switches_to_change: >
                  {{ states.switch | selectattr('entity_id', 'in', switches)
                     | selectattr('state', 'ne', new_state) | map(attribute='entity_id') | list }}
            - service: "switch.turn_{{ new_state }}"
              data:
                entity_id: "{{ switches_to_change }}"
            - wait_template: >
                {{ states.switch | selectattr('entity_id', 'in', switches_to_change)
                   | selectattr('state', 'eq', new_state) | list | length == switches_to_change | length }}
              timeout: 15
      - variables:
          switches_did_not_change: >
            {{ states.switch | selectattr('entity_id', 'in', switches)
               | selectattr('state', 'ne', new_state) | map(attribute='entity_id') | list }}
      - condition: template
        value_template: "{{ switches_did_not_change | length > 0 }}"
      - service: persistent_notification.create
        data:
          message: >
            {{ states.switch | selectattr('entity_id', 'in', switches_did_not_change)
               | map(attribute='attributes.friendly_name') | join(', ') }} did not change to {{ new_state }}

I use it like this:

- service: script.change_switches
  data:
    switches:
      - switch.xxx
    new_state: "on"

Thank you for your insight @pnbruckner! I was trying to figure out a way to overload things so the base could include error handling but I am thinking in a C++ way that does not apply here - simply rechecking the switch after trying to change it and seeing that it is unknown or not the right state, and having a variable handy to track that, seems like a good workaround, so instead of checking everything later, when the cleanup is scheduled - you know exactly what needs to be changed at that point in time instead…

1 Like