Re-run time-based automation if HA is down the first time

Which version of Home Assistant are you running?

If they are local, then the last : should be removed and the fmat should contain %z at the end. I always assumed it was not local. I don’t have any automations to check against so I’m just going off the provided timestamp strings.

EDIT: I lied, I have one. It’s stored in UTC, so the above code should work. This contains the string

This also made me realize that all these automations have been off since February…

:man_facepalming: Well, that’s embarrassing. I was thinking .day was day-of-year, not day-of-month. Thanks!!

At the point I wrote that I was so twisted up by all the various syntax rules for all the different components that I was pretty much just trying things until it worked. Which it did, so I left that part alone. I’ll clean it up once I prove the triggers and conditions are working.

There were no strings on any of my automations just before I posted, either. Now my boiler_summary is back to a string.

I’m convinced it’s got something to do with all the different ways I used to display or run it while I was debugging. Something changes it to a string somewhere along the way, and something changes it back. I’ve got no clue what that “something” might be.

It’s pretty easy to prove it changes back and forth. And I’d think, pretty hard to deny that’s a bug!

I haven’t run into that situation yet. I agree it’s best to cover all possibilities. But if it’s ever run once, like just by triggering it manually from the UI, wouldn’t that never happen?

Well, if the automation has never triggered and you restarted the system, it would be none. So yes, it shouldn’t ever be None if it’s triggered at some point. I don’t know if the last triggered is updated via manual triggers.

0.91.4

After reading all that about UTC vs local, I checked by setting up an automation to trigger a few minutes from the current local time. It kicked off as expected. So I’m guessing that fix worked.

It appeared to be, when I triggered it manually.

Yah but how is last_triggered stored for you? UTC or Local?

The example I provided might be wrong because my last triggered was sometime in feb which was an older version.

Me too. I’m running 0.89.1 and confirmed the automation’s last_triggered is in UTC.

Not sure what to make of all this now. CaptTom says he’s running 0.91.4 yet it would appear that sometimes an automation’s last_triggered is assigned a value that cannot be handled as datetime.

My understanding is an automation’s last_triggered attribute, when triggered, is written as a datetime. However, when HA restarts, it is saved and restored as a string. And it will stay a string until the next time the automation is triggered, at which time it will be overwritten as a datetime again.

The PR isn’t about an automation’s last_triggered attribute, which is always in UTC. It’s about the trigger variable, which was originally in HA’s local time zone, was accidentally changed to be in UTC for a while, and then “fixed” by the noted PR to be in HA’s local time zone again.

BTW, there’s a bug in my example template code. It should be:

{% 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 %}

Or, if you also want to see the automations that haven’t ever been triggered:

{% for x in states.automation %}
{{ x.object_id }}:
{{ x.attributes.last_triggered }} {{ x.attributes.last_triggered is string }}
{% endfor %}

No clue how it’s stored. I suppose I could learn the database layout and go look.

But using various methods to compare or display it, I have confirmed that it sometimes comes out as a string. Here’s what I got just now:

boiler_burner:
2019-04-24 14:23:49.357804+00:00 False

burner_summary:
2019-04-24T12:18:00.032182+00:00 True

close_door_switch_off:
2019-04-24 14:21:48.491908+00:00 False

open_door_switch_on:
2019-04-24 14:21:47.285095+00:00 False

The two door switch automations are just residue from some earlier dabbling. But they do work.

maybe superfluous for this discussion, still, important to note:
the last_triggered attribute is only set when the action part of the automation has been activated. Not when the trigger takes place.

An important aspect to realize when analyzing automation behavior and derived logic in HA based on last_triggered.

recently added this customize_glob to my automations, to see how many seconds ago the trigger took place.
Showing empty on all automatons not yet triggered, and counting seconds on the one’s being activated.
thought id share.

  extra_data_template: >
    if(!attributes.last_triggered) return null;
    var t,s=(new Date()-new Date(attributes.last_triggered))/1e3;
    return(
    (t=Math.floor(s/86400))?t+(t>1?" days":" day"):
    (t=Math.floor(s/3600))?t+(t>1?" hours":" hour"):
    (t=Math.floor(s/60))?t+(t>1?" minutes":" minute"):
    (t=Math.floor(s))!==1?t+" seconds":" second"
    )+" ago";

read more in this in Custom UI: Last time automation was triggered

Yup. I just restarted, and confirmed that:

boiler_burner:
2019-04-24T14:23:49.357804+00:00 True

burner_summary:
2019-04-24T12:18:00.032182+00:00 True

close_door_switch_off:
None False

open_door_switch_on:
None False

Also note that my door switches went to “None.” Proves your point about always checking for that!

Good reminder. In my example, I actually want this behavior; if the action didn’t happen yet today, I want it to happen now. But in other cases it would be the opposite.

Restarting the software causes an attribute’s type to change.

I can’t imagine an application where this would be desirable behavior. Is this worthy of logging an issue?

Issue was already created and closed: https://github.com/home-assistant/home-assistant/issues/20110

Actually, I can’t find the issue. But I recall it being created.

EDIT2: I’m just remembering something unrelated. Ignore me.

Probably.

My guess, BTW, is that the default JSON serializer being used to save data doesn’t support datetimes, so they’ve taken the easy route and converted it to a string when saving state, and on restoring, they just take the string instead of trying to convert it back to a datetime. But honestly I haven’t dug that deeply, and it’s been a while since I looked into this.

That has nothing to do with the question of how datetimes are saved and restored.

Yah, I linked wrong one.

1 Like

I looked up ‘JSON serialization datetime’ and discovered that the most common serialization function, json.dumps, doesn’t have an established way of serializing datetime.

The recommended solution is to have json.dumps call a custom function whenever it encounters a datetime object (or anything else it can’t serialize) and that function should convert datetime to a time string in ISO format.

When I look in core.restore_state, that’s exactly how all datetime objects are serialized (and not just last_triggered). The strings are UTC time in ISO format.

        {
            "last_seen": "2019-04-21T01:39:47.898162+00:00",
            "state": {
                "attributes": {
                    "friendly_name": "Input_Number Brightness Controller",
                    "last_triggered": "2019-04-21T01:20:22.541939+00:00"
                },
                "context": {
                    "id": "bc09015aeabe44d3a1afbd5ce3711620",
                    "parent_id": null,
                    "user_id": null
                },
                "entity_id": "automation.input_number_brightness_controller",
                "last_changed": "2019-04-21T01:19:37.381988+00:00",
                "last_updated": "2019-04-21T01:20:22.542120+00:00",
                "state": "on"
            }
        },

So Home Assistant’s serialization of datetime is as good as it gets. The culprit appears to be deserialization or rather the apparent lack of it. Instead of converting the time string into a datetime object it just passes it along verbatim.

So the question now is this how it ‘deserializes’ all stored time values? Although the anomaly was noticed for last_triggered it may also be true for last_changed and last_updated and all other datetime attributes.

If this is what’s happening (whether to just one or to all datetime attributes) is, to my mind, a significant error. Effectively, the deserialization process loses a data structure. It’s on par with handling a dictionary as a literal string.

I plan to experiment with this a bit more before I consider submitting it as an issue.

1 Like

Again, haven’t followed through all the code that does the saving and restoring, but I think this code is where an object’s state is restored, and you can see there are special cases of converting the string representations of last_changed & last_updated back to datetimes, but there doesn’t appear to be the same for items in the attributes dictionary.

1 Like

Thanks! Yup, it certainly looks like there’s no deserialization of attributes and they’re handled verbatim. Therefore a datetime attribute gets serialized into a nice UTC time string in ISO format but never deserialized back into a datetime object.

The only time the last_triggered attribute becomes a datetime object is when the automation is triggered.

This would explain why some last_triggered attributes appear to be a string for some automations and a datetime object for others. It all depends which automations have been triggered since the last restart.