Same automations, different behavior, last_triggered not set at startup

cnt = cnt + 1

I feel like I need to try to implment a python_script in HA to find all the pitfalls. I’m not sure why the exec() doesn’t like cnt += 1. I can run it here w/o issue…

image

exec(string) and
foo returns 1

bingo!
funny this works now, i tried it before, without luck. Apparently this change and your alterations and reorganization combined do the trick:

39

THANK you!

(now for the big question: could these changes be applied in a way my summary.py lights, switches and appliances load at startup… and have the automation have a last_triggered.

btw, i tried leaving out the initial: on so the automations last_changed would be ‘restored’ at startup, but that didn’t make a difference.

Yes, it can be done. I suggest you take a few beginner python lessons for creating methods/functions. The counter was the easy one, and you can use it as an example. YOu need to figure out what variables you would pass to the method. Then you need to write the method to behave the same way. Also, avoid naming variables ‘switches_desc’, name it ‘description’. Stuff like that.

…
point taken.

first worry now is to find a way to test for Null at startup. This is the main cause for unwanted results of the script that works just fine when initiated.

as a test I rebooted without an appliance being on, which gave me this error I hadn’t experienced before:

Error executing script: must be str, not datetime.timedelta
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "summary.py", line 354, in <module>
TypeError: must be str, not datetime.timedelta

350-354 being:

if hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered'):
    for entity_id in hass.states.get(appliances_group).attributes['entity_id']:

        dt = hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered')
        dt = dt + datetime.timedelta(hours= 2)

automation isnt yet triggered results in these errors, if dt = null the addition fails. Following your instructions I believed to test in existence with line 350 'if has.states.get etc etc'
Must not be enough…

cheers and thanks again,
Marius

isinstance(dt, str)

Try to change this one on your own. look up isinstance python on google and get used to the python documentation and stackoverflow. You can write a check to verify that dt is a str or a datetime object.

ill dive into that.

The being said, I still think the issue at hand lies mainly elsewhere: it is not so much the python script at fault (though the Null check should be implemented), as it is the fact the same automations are behaving differently. Why is the lights automation triggered, even when 0 lights are on, while switches and appliances aren’t.
Could it be these components behave differently and need special care somehow.

Maybe the easiest way would be to switch on a light, switch and appliance at startup, and have the scripts run with a small delay…
Still that would be a second best option,(adding the fact it would change the real time of last_triggered to time of startup…) if it could be handled in a more fundamental way needed because of the change in components or entities…

Cheers,
Marius

Right, but you can build in a while statement that checks the dt until it returns anything other than a string. Then it doesn’t matter if they behave differently.

Chances are that they don’t behave differently and you are just seeing a result of a multi thread software package.

really, you think that could be at hand? in that case the Null check would be the only possible cure…
just asking: would the script run at all, if a check would be in place and the dt still is a str.?

You don’t need to check for null, the error you are getting is referencing a string, not null. Python does not have null, it has None. You are not getting None:

This line is causing problems:

dt = dt + datetime.timedelta(hours= 2)

This is your error:

TypeError: must be str, not datetime.timedelta

And its basically saying you are trying to add a string to a datetime, which is not possible. The first argument is dt, so this must be a string, so it’s expecting the second datetime.timedelta to be a string. It’s not, its a datetime.timedelta object.

So, that means that this line is returning a string when it cannot find ‘last_triggered’:

dt = hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered')

This is typical for dictionaries. When it can’t find something, it returns an emtpy string.

So You need to check for a STR, not Null or None.

isisntance is a function that checks for the type of any object. It is what you want to use.

1 Like

yes, sorry about the Null, meant None :wink: Which is what i had a lot if in the first iterations, have been taken out by replacing it with if state:

now this isinstance should in fact check the dt being a datetime object shouldn’t it?

this is what is mentioned about the same issue ive been running into all this time. Hope checking with isinstance wont fail because of the same issue…

isinstance won’t work well with datetime.timedelta, it will work with datetime. So yes, you could use that.

isinstance(dt, datetime)

well, i feared as much:

Error executing script: isinstance() arg 2 must be a type or tuple of types
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "summary.py", line 354, in <module>
TypeError: isinstance() arg 2 must be a type or tuple of types

with:

if hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered'):
    for entity_id in hass.states.get(appliances_group).attributes['entity_id']:

        dt = hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered')
        if isinstance(dt, datetime):
            dt = dt + datetime.timedelta(hours= 2)
            time = '%02d:%02d' % (dt.hour, dt.minute)

whoops, was a bit and tried it anyway:

 dt = hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered')
        if isinstance(dt, datetime.timedelta):
            dt = dt + datetime.timedelta(hours= 2)
            time = '%02d:%02d' % (dt.hour, dt.minute)

makes the script run again…albeit with a wrong time…pff. Its taking the time of a setting above the section in the script, apparently the check fails, time isnt set accordingly, and it reads a time set above this (switches).
I wonder if i add this check in all 3 places, if a time gets set at all, because the check wont return true anywhere…

It doesnt. The script is finding the first time set above this, and sets the time for all sections below it accordingly…
Taking out the if isinstance(dt, datetime.timedelta): condition restores the correct timings again in all places…

So i must find some other way to have the script restore correctly at startup with no entities on. Time selection itself is correct.

Then check if its a string. It’s safer because str is builtin anyways. attributes.get should only return a float, str, int. So you could look for that.

if not isinstance(dt, (str, unicode, float, bool, int)):
1 Like

but that’s interesting, never realized that. so im getting strings all over my script and trying to add datetime …still, the time is showing correctly, or at least it seems that way, it could of course be an int disguised as a time :wink:

Shouldn’t a conversion be made then somehow? and then add the datetime.timedelta? Then again, how come it works just fine after initialization…

it works after init because the object exists, before init the object doesn’t exist so the dictionary returns whatever the default is.

As i said before, you need to learn python. This is all basic level python knowledge that you would learn with a tutorial on dictionaries.

Also, this is not necessarily true. It depends on how the dictionary was created. Most people assign simple types inside a dictionaries value

Hi @petro, this was your quote, not mine :wink:

trying to symplify here, and adhering to the fine working code in the other sections of my script, ive tried to change into this:

state = hass.states.get('automation.sense_lights_change')

#if hass.states.get('automation.sense_lights_change').attributes.get('last_triggered'):
if state:
    for entity_id in hass.states.get(lights_group).attributes['entity_id']:
        dt = state.attributes.get('last_triggered')
#        dt = hass.states.get('automation.sense_lights_change').attributes.get('last_triggered')
#        if isinstance(dt, datetime.timedelta):
        dt =  dt + datetime.timedelta(hours=2)
        time = '%02d:%02d' % (dt.hour, dt.minute) 

this is probably the exact same, but clearer to the eye, and using the same method as the other parts. Hope this makes it at least more maintainable before i learn better coding… (where to find the time)

Yes, it’s the same and it will have the same problem if the last_triggered attribute is empty or doesn’t exist. With this direction, that scenario would be less likely to occur.