Automation issue: why isn't this wait_template waiting

using:

automation:
  - alias: Cover stookhok is moving
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
      service: script.trigger

  - alias: Cover stookhok final update
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    condition:
    action:
      - delay:
          seconds: 3
      - wait_template: >
          {{(now() - state_attr('automation.cover_stookhok_is_moving','last_triggered'))
             .total_seconds() > 5}}
      - service: homeassistant.update_entity
        entity_id: binary_sensor.raamverduistering_stookhok_moving
      - service: notify.system
        data_template:
          title: Cover is moving
          message: current position is {{trigger.to_state.attributes.current_position}}

I want my binary_sensor to be updated automatically, just after the final stop of my cover (which is recorded by the trigger state on each current_position.

Idea: the automation triggers on each current_position change, and will cancel the wait, until the final one, and then update the binary_sensor. And, for checks, Ive added the notify service. What is actually happening: the notification is sent every change of state! While the wait_template isnt evaluated to true, as I can see in the developer-tools/template…

what is wring in this setup

I don’t understand your method but now() is a function. It does not update templates.

well, tbh, I am not sure what to use anymore. I simply want to update a sensor, within a few seconds after the last trigger of the automation has happened (or, few seconds after the entity has last been updated)

If I turn the wait_template into a condition, like this:

  - alias: Cover stookhok final update
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    condition: []
    action:
      - delay:
          seconds: 3
      - condition: template
        value_template: >
          {{as_timestamp(now()) - as_timestamp(states.cover.raamverduistering_stookhok.last_updated) > 3}}
##      - wait_template: >
##          {{(now() - state_attr('automation.cover_stookhok_is_moving','last_triggered'))
##             .total_seconds() > 5}}
      - service: homeassistant.update_entity
        entity_id: binary_sensor.raamverduistering_stookhok_moving # need this or otherwise it wont update
#                  the sensor after the final motion
      - service: notify.system
        data_template:
          title: Cover is no longer moving
          message: current position is {{trigger.to_state.attributes.current_position|int}} %

nothing ever happens again…

thing is, I would rather trigger off the binary_sensor:

  - platform: template
    sensors:
      raamverduistering_stookhok_moving:
        friendly_name: Stookhok moving
        entity_id: script.trigger, cover.raamverduistering_stookhok, automation.cover_stookhok_is_moving
        value_template: >
          {{(now() - states.cover.raamverduistering_stookhok.last_updated)
             .total_seconds() < 2}}
        device_class: moving

and not need the update_entity at all. Unfortunately, this binary_sensor will only immediately turn on, and doesn’t turn off when the covers are changing position…

HA processes templates to look for entity IDs and then re-evaluates them every time state changes for one of the tracked entity IDs. now() is not an entity ID so essentially HA is only going to recalcuate your template when state changes for cover.raamverduistering_stookhok (or in the case of your first one automation.cover_stookhok_is_moving).

So it seems like your logic doesn’t really have a way of working right now. The problem with your first wait_template was that you were comparing apples to oranges. You were subtracting the total_seconds (a single number) from now() (a datetime object) which isn’t really meaningful logic. I guess it was always returning true but its kind of just an undefined situation. The problem with your second is that any time that condition is actually being evaluated its going to be less then 5 seconds from the last time the state of cover.raamverduistering_stookhok changed so the condition will evaluate to false and that will be the end of the script.

Based on my understanding of your problem, I’m going to suggest a different approach. For the automation, do this:

automation:
  - alias: Cover stookhok is moving
    trigger:
    - platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
    - service: script.turn_off
      data:
        entity_id: script.cover_stookhok_final_update
   - service: script.cover_stookhok_final_update

And then in the script do this:

script:
  - alias: Cover stookhok final update
    sequence:
      - delay:
          seconds: 5
      - service: homeassistant.update_entity
        entity_id: binary_sensor.raamverduistering_stookhok_moving
      - service: notify.system
        data_template:
          title: Cover is moving
          message: current position is {{trigger.to_state.attributes.current_position}}

What that will do is every time the cover changes it will stop the script and then start it again. The script itself will first delay for 5 seconds before proceeding with the update and notification. It will only be able to proceed past the delay if its been more then 5 seconds since the last state change of cover.raamverduistering_stookhok. Which to my understanding is something that only occurs if the cover has stopped moving.

Let me know if this is what you’re trying to do? Like @tom_l I’m a bit confused by the actual automations you’re sharing but mostly going off your description of the problem and what you want to happen.

Hi, and thanks for chiming in!, appreciated.

well, no. tbh, both templates work as expected in the template editor… and I can see them change correctly, as long as I press enter.

also, triggering of the state of the cover works, as long as its changing state (or any of its attributes change). The issue is only, how to trigger the final update, when the cover has actually stopped changing position.

So I am trying to calculate that based on the last_updated of the cover entity, or last_triggered of the automation…

btw, this is the binary_sensor:

  - platform: template
    sensors:
      raamverduistering_stookhok_moving:
        friendly_name: Stookhok moving
        entity_id:
          - cover.raamverduistering_stookhok
          - automation.cover_stookhok_is_moving
        value_template: >
          {{(now() - states.cover.raamverduistering_stookhok.last_updated)
             .total_seconds() < 2}}

for full disclosure, here is where I try to explain the fuller setup. Was hoping I could focus in this thread…:wink:

Right but the problem here is that this kind of logic isn’t a trigger, its a condition. You can trigger events off of things like time of day, state changing in an entity, event fired, etc. You can condition off of things like entity in a certain state, or entity hasn’t been updated in the past x seconds/minutes/hours. But what you can’t do is trigger off logic like ‘entity changed states x seconds/minutes/hours’ ago. There’s no event fired for ‘an entity changed states x amount of time ago’ so there’s nothing to trigger a script off of.

This is my main issue with the current logic. You’re trying to wait for something which isn’t a trigger. Yes your template works in the template editor but as you said, only when you press ‘enter’. That’s because pressing enter is effectively your event/trigger there forcing recalculation. In an automation you’ll be missing that trigger.

So that’s why instead I suggested my proposal. Where your automation kicks off a script on state change but only lets it finish if its been more then 5 seconds since the last state change (i.e. cover movement has finished). It still sounds like something like that would work would it not?

It seems you’re going about this the hard way. Why not use an input_boolean instead of a binary_sensor, then a timer and a couple of automations:

input_boolean:
  raamverduistering_stookhok_moving:
timer:
  raamverduistering_stookhok_moving:
    duration: 5
automation:
- trigger:
  - platform: state
    entity_id: cover.raamverduistering_stookhok
  action:
  - service: input_boolean.turn_on
    entity_id: input_boolean.raamverduistering_stookhok_moving
  - service: timer.start
    entity_id: timer.raamverduistering_stookhok_moving
- trigger:
  - platform: event
    event_type: timer.finished
    event_data:
      entity_id: timer.raamverduistering_stookhok_moving
  action:
  - service: input_boolean.turn_off
    entity_id: input_boolean.raamverduistering_stookhok_moving

EDIT: For “frontend” purposes, you could always layer a template binary sensor on top of the input boolean:

binary_sensor:
- platform: template
  sensors:
    raamverduistering_stookhok_moving:
      value_template: >
        {{ is_state('input_boolean.raamverduistering_stookhok_moving', 'on') }}

EDIT 2: I don’t know what your cover entity looks like and how it behaves, but if other attributes might change besides current_position which might cause this not to work correctly, you could always add the following to the first automation:

  condition:
  - condition: template
    value_template: >
      {{ trigger.to_state.attributes.current_position !=
         trigger.from_state.attributes.current_position }}
2 Likes

Oh right, I always forget about timer. That does seem more appropriate here then a script with a delay.

thanks Phil, will implement that after dinner :wink:

I fear this will be the only way. Which still feels rather inadequate. Why can we automate on motion, but not on the halting of that motion directly.
Ive already FR’d the addition of attributes ‘opening’ and ‘closing’ which apparently ought to be on all covers, but lack in this IKEA integration. That would solve the issue at the source level.

ending for now with this, which only triggers on ‘off’, because of the sensor.time. ‘on’ is fine:

  - platform: template
    sensors:
      raamverduistering_stookhok_moving:
        friendly_name: Stookhok moving
        entity_id:
          - sensor.time
          - cover.raamverduistering_stookhok
          - automation.cover_stookhok_is_moving
        value_template: >
          {{(now() - states.cover.raamverduistering_stookhok.last_updated)
             .total_seconds() < 2}}
#          {{is_state('binary_sensor.raamverduistering_stookhok_opening','on') or
#            is_state('binary_sensor.raamverduistering_stookhok_closing','on')}}
        device_class: moving


automation:
  - alias: Cover stookhok is moving
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
      service: script.trigger

  - alias: Cover stookhok final update
    trigger:
      platform: state
      entity_id: binary_sensor.raamverduistering_stookhok_moving
      to: 'off'
    condition: []
    action:
#      - delay:
#          seconds: 3
#      - condition: template
#        value_template: >
#          {{as_timestamp(now()) - as_timestamp(states.cover.raamverduistering_stookhok.last_updated) > 3}}
##      - wait_template: >
##          {{(now() - state_attr('automation.cover_stookhok_is_moving','last_triggered'))
##             .total_seconds() > 5}}
#      - service: homeassistant.update_entity
#        entity_id: binary_sensor.raamverduistering_stookhok_moving # need this or otherwise it wont update
#                  the sensor after the final motion
      - service: notify.system
        data_template:
          title: Cover is no longer moving
          message: current position is {{state_attr('cover.raamverduistering_stookhok','current_position')|int}} %

Automations are triggered by events. Something happening is an event. Something not happening is the absence of an event. How can something be triggered by something not happening???

Not sure I understand why you “fear” a valid, reasonable solution. It is creating the event you’re looking for. I.e., when the timer expires, then the cover hasn’t moved in the specified time period, and that’s the “event” you want.

I don’t use covers, and I’m not that familiar with how they generally work, but yes, I’d agree that this is something the cover entity should do – i.e., indicating it’s moving or not. This isn’t in the state? I.e., open, closed, opening, closing, …???

Regarding you last solution, why would this not work (i.e., without the need for the automations/scripts):

binary_sensor:
  - platform: template
    sensors:
      raamverduistering_stookhok_moving:
        friendly_name: Stookhok moving
        entity_id:
          - sensor.time
          - cover.raamverduistering_stookhok
        value_template: >
          {{(now() - states.cover.raamverduistering_stookhok.last_updated)
             .total_seconds() < 2}}
        device_class: moving

Using Timer as mention, can you use something like this:

timer:
  raamverduistering:
    duration: '00:00:05'
    icon: mdi:timer-sand

automation:
  - alias: Cover stookhok is moving
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
      - service: timer.cancel
        entity_id: timer.raamverduistering
      - service: timer.start
        entity_id: timer.raamverduistering

  - alias: Cover stookhok final update
    trigger:
      - platform: event
        event_type: timer.finished
        event_data:
          entity_id: timer.raamverduistering
    action:
      - service: notify.system
        data_template:
          title: Cover is no longer moving
          message: current position is {{trigger.to_state.attributes.current_position|int}} 

It should be similar to the “waiting” logic you have used. Perhaps change the timer to 10sek (or something) if it’s not working?

Edit: I didn’t look at the last automation…

Canceling the timer is unnecessary. If it is started while running it will effectively start over.

Oh. I Didn’t know that.
I posted this code as I not long ago did something similar, and this was the code I used (I of course adapted it to this example).
I also see that my code doesn’t really add anything compared to your code further up the thread. But hey, nobody complains about to many examples :slightly_smiling_face:

1 Like

of course it can’t. No, I was looking for the end of the triggering event. But that will probably be clear…

Well, fear might be a bit heavy. Surprised would be better? that we have to create the even manually, use a timer with a hardcoded length, instead of the integration doing that in core. We cant use a template like in the media_play.play media where we use the length of the media file. Here we have to estimate. Since the automation triggers each second, I figured 2 seconds would be enough for the ‘off’ event to be created. But that was just a bit sharp. restarting for 3 now… :wink:

It would be really nice to have an event_stopped event in HA core. We have many events that are binary, these are clear. This one isn’t, (clear nor digital) but would warrant its own

No, its state is always open or closed, hence my FR to change that, as apparently other cover integrations do. Or at least have an attribute.

I dont know, I ave been struggling with this all day, in many variants. it simply doesnt track the value_template, other than on sensor.time. Which can take up to a minute. Which of course is unacceptable for a frontend representation.

the input_boolean is a nice technique indeed. Just too bad it isn’t using the new Script logic :wink: was really happy to use the script and mode: parallel as I had posted it…

I don’t get what you want the “core” to do.

An “event stopped” event? What is that? What does that even mean? And what do you mean by “events that are binary”? An event is something that ocurrs in an instant, it’s not off & on.

I think you mean you want “something” to monitor for a particular event, and after it sees it, it waits until it doesn’t see it for at least X seconds, and when it doesn’t it does something (fire another event, trigger an automation, etc.) And then it starts monitoring for the event again, …

FWIW, you could probably do something like this (using the new automation capabilities that are hopefully coming soon):

automation:
- trigger:
  - platform: state
    entity_id: cover.raamverduistering_stookhok
  mode: restart
  action:
  - delay: 3
  - service: ...

Every time cover.raamverduistering_stookhok changes state (or an attribute changes) the action will start running, or restart running. So the service step would only be executed if the entity changed at least once, and then didn’t change for at least 3 seconds.

Is that what you’re wanting?

And since the new modes will only be available in scripts first, then you could do this instead:

automation:
- trigger:
  - platform: state
    entity_id: cover.raamverduistering_stookhok
  action:
  - service: script.my_script
script:
  my_script:
    mode: restart
    sequence:
    - delay: 3
    - service: ...

Of course you can use the timer.start service and use a template to specify the duration. It doesn’t have to be hard coded.

for starters, this works now, perfection. So a big thanks is in order! thanks.

automation:
  - alias: Cover stookhok is moving
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
#      service: script.trigger
      - service: input_boolean.turn_on
        entity_id: input_boolean.raamverduistering_stookhok_moving
      - service: timer.start
        entity_id: timer.raamverduistering_stookhok_moving

  - alias: Cover stookhok no longer moving
    trigger:
      - platform: event
        event_type: timer.finished
        event_data:
          entity_id: timer.raamverduistering_stookhok_moving
    condition: []
    action:
      - service: input_boolean.turn_off
        entity_id: input_boolean.raamverduistering_stookhok_moving
      - service: notify.system
        data_template:
          title: Cover is no longer moving
          message: >-
            Cover is
            {%- set position = state_attr('cover.raamverduistering_stookhok','current_position')|int %}
            {%- set phrase = 'positioned at: ' %}
            {%- if is_state('cover.raamverduistering_stookhok','closed')%} Closed
            {%- elif position|int == 100 %} Open
            {%- else %} {{phrase}}{{position}}
            {% endif %}

and, this customization reflects that in the frontend:

homeassistant:
  customize:
    cover.raamverduistering_stookhok:
      templates:
        icon: >
          if (entities['binary_sensor.raamverduistering_stookhok_moving'].state == 'on') return 'mdi:animation';
          if (state == 'open' && attributes.current_position == 100) return 'mdi:window-shutter-open';
          if (state == 'closed') return 'mdi:window-shutter';
          else return 'mdi:window-shutter-alert';
        icon_color: >
          if (entities['binary_sensor.raamverduistering_stookhok_moving'].state == 'on') return 'red';
          if (state == 'open' && attributes.current_position == 100) return 'gold';
          if (state == 'closed') return 'darkblue';
          else return 'green';

not sure this is true, at least not in the way I meant it to. I tried to express the desire to template the time it takes to go from moving to not moving. Might be a chicken an egg issue…

yes, an event stopped. the event would be state_changed_to_moving. and state changed from moving to not moving. or playing, or any other event that takes more time than the instant.

But, maybe this is all caused by the integration not correctly stating the opening/closing.

I’d simply like to trigger as we can do with a light. turn_on/off, and between it is glowing. these event are missing here. I can only trigger on the attribute current_position to be changing…

hmm, I guess yes, will have to try and see if that ends up doing the same :wink: How would the binary_sensor need to be templated with this? because that’s the end goal, have a binary change state, and show the frontend the cover is moving or not.

Again, use an input_boolean, and then layer a binary_sensor on top of it if you want.

automation:
- trigger:
  - platform: state
    entity_id: cover.raamverduistering_stookhok
  action:
  - service: script.my_script
script:
  my_script:
    mode: restart
    sequence:
    - service: input_boolean.turn_on
      entity_id: input_boolean.raamverduistering_stookhok
    - delay: 3
    - service: input_boolean.turn_off
      entity_id: input_boolean.raamverduistering_stookhok
1 Like

Smooth! can confirm this works very nicely indeed (and takes advantage of the (future) new scripting options :wink: )

script:
  cover_stookhok:
    alias: Cover stookhok
    mode: restart
    sequence:
      - service: input_boolean.turn_on
        entity_id: input_boolean.raamverduistering_stookhok_moving
      - delay: 2
      - service: input_boolean.turn_off
        entity_id: input_boolean.raamverduistering_stookhok_moving
      - condition: state
        entity_id: input_boolean.notify_developing
        state: 'on'
      - service: notify.system
        data_template:
          title: Cover is no longer moving
          message: >-
            {{as_timestamp(now())|timestamp_custom('%X')}} - Cover is
            {%- set position = state_attr('cover.raamverduistering_stookhok','current_position')|int %}
            {%- set phrase = 'positioned at: ' %}
            {%- if is_state('cover.raamverduistering_stookhok','closed')%} Closed
            {%- elif position|int == 100 %} Open
            {%- else %} {{phrase}}{{position}}
            {% endif %}

automation:
  - alias: Cover stookhok
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
      service: script.cover_stookhok

I’ve changed the solved tick to the last post Phil, as I hope it will help many users finding a correct loop with the mode: restart option.

update

only ‘issue’ I experience with this technique is it gets triggered at startup/restart…while using the timer automations did not do that. hmm

so a little more experimenting seems to allow for determining ‘opening’ and ‘closing’, by adding 1 script.cover_stookhok_position, and adding that in the sequence before the existing one:

script:
  cover_stookhok_position:
    alias: Cover stookhok position
    mode: restart
    sequence:
      - condition: template
        value_template: >
          {{toposition != fromposition}}
      - service: input_boolean.turn_on
        data_template:
          entity_id: >
            input_boolean.{{'cover_stookhok_opening' if toposition > fromposition else 'cover_stookhok_closing'}}
# below not needed because booleans are turned off anyway. and always start from 'off'
#      - service: input_boolean.turn_off
#        data_template:
#          entity_id: >
#            input_boolean.{{'cover_stookhok_opening' if toposition < fromposition else 'cover_stookhok_closing'}}

automation:
  - alias: Cover stookhok
    trigger:
      platform: state
      entity_id: cover.raamverduistering_stookhok
    action:
      - service: script.cover_stookhok_position
        data_template:
          toposition: '{{trigger.to_state.attributes.current_position}}'
          fromposition: '{{trigger.from_state.attributes.current_position}}'
      - service: script.cover_stookhok

this in turn allows me to customize the icon even better, and, instead of the ‘motion’ icon, show ‘archive-arrow-down’ and ‘archive-arrow-up’ :wink:
which is a nice and unforeseen use of these ‘Archive’ icons…

homeassistant:
  customize:
    cover.raamverduistering_stookhok:
      templates:
        icon: >
          if (entities['input_boolean.cover_stookhok_opening'].state == 'on') return 'mdi:archive-arrow-up';
          if (entities['input_boolean.cover_stookhok_closing'].state == 'on') return 'mdi:archive-arrow-down';
          if (state == 'open' && attributes.current_position == 100) return 'mdi:window-shutter-open';
          if (state == 'closed') return 'mdi:window-shutter';
          else return 'mdi:window-shutter-alert';
        icon_color: >
          if (entities['binary_sensor.raamverduistering_stookhok_moving'].state == 'on') return 'red';
          if (state == 'open' && attributes.current_position == 100) return 'gold';
          if (state == 'closed') return 'midnightblue';
          else return 'green';