I’m trying to setup an automation so that my vacuum runs when both of us have left the house, but is limited to once per day. I came up with the following snippet based on code from elsewhere on this forum.
- alias: 'Start cleaning when everyone is away'
trigger:
platform: state
entity_id: group.people
to: 'not_home'
condition:
condition: and
conditions:
- condition: template
value_template: '{{ now().day != states.automation.start_cleaning_when_everyone_is_away.attributes.last_triggered.day | default(0) }}'
action:
- service: vacuum.set_fan_speed
data:
entity_id: vacuum.xiaomi_vacuum_cleaner
fan_speed: 100
- service: vacuum.start
entity_id: vacuum.xiaomi_vacuum_cleaner
This works ok and my initial testing looked like it worked, but it ran for a second time after running once in the morning. I think this is because it became a new day in UTC at around midday Australian time.
Does anyone have any advice on how to fix this up?
The problem is that the last_triggered attribute is a string, not a Python datetime object, so you can’t use the day method. You could try something like this:
condition:
- condition: template
value_template: >
{% set last = state_attr('automation.start_cleaning_when_everyone_is_away',
'last_triggered') %}
{{ last is none or
as_timestamp(last)|timestamp_custom('%j')|int !=
now().strftime('%j')|int }}
This will be true if the automation has never been triggered, or if it has been triggered, but not today. it will be false if the automation was last triggered today.
Note that the %j format string means the day of the year (i.e., 1 through 365.) Also, I use now() instead of utcnow() because timestamp_custom, by default, uses local time.
Is there any way to run this interactively/debug/through a console so that one can get their head around it?
i.e. if someone wanted to know, at a glance, what the current state value of last_triggered was, or to test what as_timestamp(last) does, what would be the recommended way of doing this.
I know that you can look at the entity last_triggered using the entity browser but how would you quickly see how it behaves when passed to the as_timestamp function?
I’ve actually noticed that last_triggered is sometimes a string and sometimes a datetime object. It’s really odd. I can’t quite put my finger on why it’s happening.
{% for x in states.automation if x.attributes.last_triggered is not none %}
{{ x.object_id }}:
{{ x.attributes.last_triggered }} {{ x.attributes.last_triggered is string }}
{% endfor %}
Sure enough, some are strings, and some are not. The ones that are strings look like this:
2019-02-25T13:00:00.251124+00:00
Whereas the ones that aren’t (and which are datetimes) look like this:
2019-02-25 19:38:52.049082+00:00
That is nuts! I never noticed this before.
I checked the code and it looks like the attribute is written as a datetime (from utcnow()). I’m guessing this is because of the relatively recent change to saving & restoring states. Wow, if it is, if you ask me, that’s a bug!!
EDIT: I wonder how many of my automations are broken by this???
Yeah, I stumbled on it helping @Mariusthvdb the other day. It’s the weirdest thing. I don’t know where it’s coming from… It really screwed up @Mariusthvdb’s templates too.
Lol, in typical fashion, I ignored it! I suppose I could write up a PR. It really didn’t cross my mind that it’s a bug.
I wrote up an issue about trigger.now changing from local time to UTC time, which breaks automations. The reaction by the person who made the change that caused this side effect said, well, it’s not documented, so too bad. Really makes one want to take the time to report problems.
Interesting. I know I’ve used last_triggered in automations before, but apparently I don’t currently use it in any. So I guess the answer is zero. On the bright side, converting it to a timestamp using as_timestamp works whether it’s a datetime or a string.
I haven’t tried to get more visibility on it (not even sure how) since it’s simple enough to fix, IMHO, but I’ve just been too busy to get around to doing it myself. Besides, one would think the person that broke it would want to take responsibility for what they did (rather than come up with a lame excuse.)
EDIT: BTW, to work around the problem, rather than just doing:
now().hour
in a template, I now have to do:
{% set hr = as_timestamp(trigger.now)|timestamp_custom('%H')|int %}
Just what my little Pi needs – more work for no good reason.
Look in my example, where I needed the last_triggered of the actual automation itself to have passed a certain time.
Using that condition in the condition section of the automation, it won’t pass (since it hasn’t been set yet), and action section isn’t called. The attribute last_triggered isn’t set, and hence all templates relying on that will fail, and shows the T.
Using that condition in the action part though, makes it all work, since arriving at the action part of the automation sets the last_triggered attribute. T gone.
Ive learned:
it might be a bug, and how to resolve that:
using conditions in the action part, which has the real added advantage of being able to test an automation in HA by manually triggering it and evaluating all conditions (which doesnt happen in a regular setup, because manually triggering immediately jumps to the action section of an automation )
last_triggered, should really be called last_called_action, since that’s what it is showing right now. The last time the action section of an automation is called.
put differently: last_triggered isn’t truly last_triggered, since either triggering the automation manually or automated in HA real life, the attribute isn’t set if not entering the action phase of the automation, meaning it doesnt look at the trigger moment, but at the action moment…
I achieved this by ensuring the Mode of my automation is set to “Single” and then adding a wait_for_trigger at the end of my automation, waiting for midnight. This will essentially prevent the automation from running the rest of the day.
wait_for_trigger:
- platform: time
at: '00:00:00'
continue_on_timeout: false
Be aware that the longer anything waits (like wait_for_trigger, wait_template, for, delay, etc) the more susceptible it is to being reset by Reload Automations or a restart.
Thanks for the tip. In my case, this is an acceptable risk as the worst case is that my vacuum starts again. But, I rarely reload automations or restart the system so I like the simplicity of the wait_for_trigger.