Help with first python_script

Hi,

I’m trying to set up my first python script (for motion sensor light control). But I’ve obviously messed up something.

I’m far from experienced, so maybe there’s (in addition to an error or 10) a better/more slick way of doing this that I haven’t learned about yet. If so, I’d also be interested in hearing about that!

My motion_light.py (located in python_scripts directory):

# Script to control the lights based on motion sensor data

# Get current time in HH:MM:SS format
now                 = datetime.datetime.now()
time                = "{}:{}:{}".format(now.hour, now.minute, now.second)

# Variables to set
workday             = ""
areWeUp             = ""
areKidsUp           = ""

# Get Data from Automation Trigger
triggeredEntity     = data.get('entity_id')

# Are we up and awake? Test by dining table lights. Change this once I get a better proxy, e.g. bed sensors.
if hass.states.get("light.dining_table_lights") == "on":
    areWeUp = "yes"
else:
    areWeUp = "no"

'''
# Are people on top floor up? Test by top floor living room lights once they are properly installed
if hass.states.get("light.top_floor_living_room") == "on":
    areKidsUp = "yes"
else:
    areKidsUp = "no"
'''

# Get states of motion sensors
entrance_motion_state = hass.states.get("binary_sensor.motion_sensor_158d00023e3742")
basement_entrance_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000200d203")
basement_stairway_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000236a0f3")
tv_room_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000236a116")
conservatory_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000200d285")
bathroom_1_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000200e0c5")
bathroom_2_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000236a22f")
bathroom_upstairs_motion_state = hass.states.get("binary_sensor.motion_sensor_158d000236a0d0")


## Bathrooms

# Main floor bathroom.
while (
    hass.states.get("binary_sensor.motion_sensor_158d000200e0c5") == "on" or
    hass.states.get("binary_sensor.motion_sensor_158d000236a22f") == "on":
    )
    if is_time_between(time, ("07:00", "22:00")) == true:
        if hass.states.get("sensor.illumination_158d000236a22f") < "50"
            hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between(time, ("22:00", "07:00")) == true:
        if areWeUp == "no":
            if hass.states.get("sensor.illumination_158d000236a22f") < "50"
                hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'on', 'brightness': '10', 'rgb_color': [255,0,0] })
        elif areWeUp == "yes":
            hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'on', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'off' })

# Upstairs bathroom. Add a "areKidsUp" check once new lamps are installed upstairs
while (
    hass.states.get("binary_sensor.motion_sensor_158d000236a0d0") == "on":
    )
    if is_time_between(time, ("07:00", "21:00")) == true:
        if hass.states.get("sensor.illumination_158d000236a0d0") < "100"
            hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between(time, ("21:00", "07:00")) == true: # add "if areKidsUp == "no":" under here:
        if hass.states.get("sensor.illumination_158d000236a0d0") < "100"
            hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'on', 'brightness': '10', 'kelvin': '2200' }) # add "elif areKidsUp == "no":" under here:
        else:
            hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'on', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', action, service_data={ 'entity_id': 'light.bathroom', 'state': 'off' })


## Entrance and basement

# Entrance
while (
    hass.states.get("binary_sensor.motion_sensor_158d00023e3742") == "on": # entrance motion
    )
    if is_time_between(time, ("07:00", "21:00")) == true:
        hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
        hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between(time, ("21:00", "07:00")) == true: # add "if areKidsUp == "no":" under here:
        if areWeUp == "yes":
            hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
            hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
        elif areWeUp == "no":
             hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance', 'state': 'on', 'brightness': '10', 'kelvin': '2200' })
else:
    hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000236a0f3") == "off": # Basement stairway motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'off' })
        if hass.states.get("binary_sensor.motion_sensor_158d000200d203") == "off":
            hass.services.call('light', action, service_data={ 'entity_id': 'light.basement_entrance_lights', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000200d203") == "off": # Basement entrance motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.basement_entrance', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000236a116") == "off": # TV room motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.tv_room', 'state': 'off' })

# Basment entrance
while (
    hass.states.get("binary_sensor.motion_sensor_158d000200d203") == "on": # Basement entrance motion
    )
    hass.services.call('light', action, service_data={ 'entity_id': 'light.basement_entrance_lights', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', action, service_data={ 'entity_id': 'light.basement_entrance', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000236a0f3") == "off": # Basement stairway motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000236a116") == "off": # TV room motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.tv_room', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d00023e3742") == "off": # Entrance motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance_lights', 'state': 'off' })
        hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'off' })

# TV room
while (
    hass.states.get("binary_sensor.motion_sensor_158d000236a116") == "on": # TV room motion
    )
    # Add an "if", so this automation is disabled if the TV room TV is on.
    hass.services.call('light', action, service_data={ 'entity_id': 'light.light.tv_room', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', action, service_data={ 'entity_id': 'light.tv_room', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000200d203") == "off": # Basement entrance motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.basement_entrance_lights', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000236a0f3") == "off": # Basement stairway motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d00023e3742") == "off": # Entrance motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance_lights', 'state': 'off' })

# Basement stairway
while (
    hass.states.get("binary_sensor.motion_sensor_158d000236a0f3") == "on": # Basement stairway motion
    )
    hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'on', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', action, service_data={ 'entity_id': 'light.stairway', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000200d203") == "off": # Basement entrance motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.basement_entrance', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d000236a116") == "off": # TV room motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.tv_room', 'state': 'off' })
    if hass.states.get("binary_sensor.motion_sensor_158d00023e3742") == "off": # Entrance motion
        hass.services.call('light', action, service_data={ 'entity_id': 'light.entrance_lights', 'state': 'off' })

I tested with various combinations of motion sensors being on/off, and none produced a response (light toggle).

My automation to trigger:

- alias: motion light python script
  trigger:
    - platform: state
      entity_id: 'binary_sensor.motion_sensor_158d00023e3742, binary_sensor.motion_sensor_158d000200d203, binary_sensor.motion_sensor_158d000236a0f3, binary_sensor.motion_sensor_158d000236a116, binary_sensor.motion_sensor_158d000200d285, binary_sensor.motion_sensor_158d000200e0c5, binary_sensor.motion_sensor_158d000236a22f, binary_sensor.motion_sensor_158d000236a0d0'
  action:
    - service: python_script.motion_light

I also tried using if instead of while (since it triggers based on any state change of sensors, if should be fine?).

Learning python_script myself, does any of the hass.services.call work by itself?

https://www.home-assistant.io/components/python_script/#calling-services had call like:

hass.services.call('light', 'turn_on', service_data, False)

All the examples I saw, did not have the “service_data=”, maybe test a single line of hass.services.call to be sure it work first?

Don’t name your time the word time. time is a library in python. It may already be loaded, and you doing this will overwrite it. Use current_time or something. Also, this is the incorrect way to convert a datetime object to a timestring. Use strfttime.

current_time                = now.strftime("%H:%M:%S")

Did you make this function? I don’t see where you declare it. Also, is that function smart enough to compare a “%H:%M” against a “%H:%M:%S”? I’d assume it wouldn’t be, meaning you should add seconds.

This else if is kinda pointless. Just make it else. You’re searching for the opposite of the if statement.

Unless i’m blind, I don’t see you declare action anywhere.

Other things to point out:

  • Missing quote at the end of if statements. You have alot of these. Double check them all.
  • You can’t compare strings like this. You need to convert the state to an integer and compare it to an integer. Comparing strings will not result in what you expect.
  • that is not the correct syntax for true/false. You need to use a capital T and capital F. True or False.
  • if the is_time_between returns a boolean (True/False) you don’t even need to declare it. Just do if is_time_between():

These functions should help you with what is listed above:

#quick way to convert a time string to an integer.
def split_time(t):
    """ Expects a time string formatted 00:00:00 or 00:00 or None"""
    if t != None:
        try:
            ts = [ int(t) for t in t.split(':') ]
        except ValueError:
            ts = [ 0, 0, 0 ]
    else:
        now = datetime.datetime.now()
        ts = [ now.hour, now.minute. now.second ]
        
    return sum([ t * convert for t, convert in zip(ts, [3600, 60, 0 ])])

#makes your time comparison, returns true/false.
def is_time_between(first_time, second_time, compared_time = None):
    ct = split_time(compared_time)
    t1 = split_time(first_time)
    t2 = split_time(second_time)
    return t1 <= ct <= t2

# This is a quick test to return a true/false if the device is on.
def is_on(entity_id): return hass.states.get(entity_id) == 'on'
   
def getIntegerState(entity_id):
    try:
        return int(hass.states.get(entity_id))
    except ValueError:
        return 0

To use them:

Quickly check if an entity is on

if is_on("light.dining_table_lights"):

Check the current time against 2 times:

    if is_time_between("07:00", "22:00"):

Check any time against 2 times:

    if is_time_between("07:00", "22:00", "08:00:00"):

Get a state as an integer and check to see if it’s below 50:

if getIntegerState("sensor.illumination_158d000236a22f") < 50:

Thanks guys.

@JTPublic I think you’re right, the service calls didn’t work.

@petro Yup, good eyes. I forgot to change the action to 'turn_on'. I did that, and deleted the 'state': 'on' from the service call, and voila!

hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.dining_table_lights', 'brightness': '255', 'kelvin': '2700' })

now works.

@petro good point about the time; I do vaguely remember reading this at some point. The is_time_between is not currently being used, I just grabbed this from somewhere (see link in code), because I thought I might want to use it later.
I wanted to eventually have more than two times checked (day/evening/night), that’s why I have the elif in already. It shouldn’t break it as is though, right?
And you’re absolutely right about the action; I just blindly copied and forgot to change. I’m using it as in the example above (not defining action, just writing 'turn_on' instead.)

Yes, I did start to go through some of the syntax errors I found. Thanks, I’ll do a rewrite with your input and see if that helps!

I cut to the bone, and I think it’s actually getting the state of the sensors was an issue. I needed to compare

if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == 'on':

instead of

if hass.states.get("binary_sensor.motion_sensor_158d000200d285") == 'on':

I can’t use the strftime; if I do, I get an error:

Error executing script: '__import__'
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "motion_light_2.py", line 26, in <module>
KeyError: '__import__'

referring to the line with strftime.

I guess that means I should re-write for appdaemon instead, where I can import libraries, if I want to use that?

I also get an error with split_time. I made a script to test, incorporating your suggestions:

def split_time(t):
    if t != None:
        try:
            ts = [ int(t) for t in t.split(':') ]
        except ValueError:
            ts = [ 0, 0, 0 ]
    else:
        now = datetime.datetime.now()
        ts = [ now.hour, now.minute. now.second ]

    return sum([ t * convert for t, convert in zip(ts, [3600, 60, 0 ])])

#makes your time comparison, returns true/false.
def is_time_between(first_time, second_time, compared_time = None):
    ct = split_time(compared_time)
    t1 = split_time(first_time)
    t2 = split_time(second_time)
    return t1 <= ct <= t2

# This is a quick test to return a true/false if the device is on.
def is_on(entity_id): return hass.states.get(entity_id) == 'on'

def getIntegerState(entity_id):
    try:
        return int(hass.states.get(entity_id))
    except ValueError:
        return 0

areWeUp             = ""
if hass.states.get("light.dining_table_lights").state == "on":
    areWeUp = "yes"
else:
    areWeUp = "no"

# Main floor bathroom.
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == "on":
    if is_time_between("07:00", "22:00"):
        hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between("22:00", "07:00"):
        if areWeUp == "no":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '10', 'rgb_color': [255,0,0] })
        elif areWeUp == "yes":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_off', service_data={ 'entity_id': 'light.bathroom' })


# Conservatory
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == 'on': # Conservatory motion
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '10', 'kelvin': '4000' })

If the motion_sensor is off, I get the expected result, but if it’s on, nothing happens, and I get these errors:

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/helpers/service.py", line 224, in _handle_service_platform_call
    await getattr(entity, func)(**data)
  File "/usr/src/app/homeassistant/components/light/hue.py", line 388, in async_turn_off
    await self.light.set_state(**command)
  File "/usr/local/lib/python3.6/site-packages/aiohue/lights.py", line 75, in set_state
    json=data)
  File "/usr/local/lib/python3.6/site-packages/aiohue/bridge.py", line 70, in request
    ) from None
aiohue.errors.RequestError: Error requesting data from 192.168.0.10: Cannot connect to host 192.168.0.10:80 ssl:None [Connection reset by peer]

and weirdly, an error:

Error executing script: name 'split_time' is not defined
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "test_2.py", line 40, in <module>
  File "test_2.py", line 18, in is_time_between
NameError: name 'split_time' is not defined

Even though it is the very first definition in the script??

Ok, you don’t really need it though if you use the split_time.

I believe the call service is incorrect then: get rid of service_dict= in each call. Just pass the dictionary.

Ah, i see a typo.

def split_time(t):
    if t != None:
        try:
            ts = [ int(t) for t in t.split(':') ]
        except ValueError:
            ts = [ 0, 0, 0 ]
    else:
        now = datetime.datetime.now()
        ts = [ now.hour, now.minute, now.second ]

    return sum([ t * convert for t, convert in zip(ts, [3600, 60, 0 ])])

Maybe I’m just going blind, but I don’t see it? I copy/paste’d your latest split_time, but get the same error.

I think actually this might also be due to the split time. If I just comment that out, the rest of the (simple test) script works as expected; it turns on the light/color/brightness expected depending on the state of the motion sensor:

if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == "on":
#    if is_time_between("07:00", "22:00"):
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '255', 'kelvin': '2700' })
#    elif is_time_between("22:00", "07:00"):
    if areWeUp == "no":
        hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '10', 'rgb_color': [255,0,0] })
    elif areWeUp == "yes":
        hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_off', service_data={ 'entity_id': 'light.bathroom' })

bad line: ts = [ now.hour, now.minute. now.second ]
good line: ts = [ now.hour, now.minute, now.second ]

What’s the first error?

I get the same error even after correcting the period-to-comma:

Error executing script: name 'split_time' is not defined
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "test_2.py", line 41, in <module>
  File "test_2.py", line 19, in is_time_between
NameError: name 'split_time' is not defined

For reference, this is the python script that gives the error:

# Test scripts

#quick way to convert a time string to an integer. Expects a time string formatted 00:00:00 or 00:00 or None
def split_time(t):
    if t != None:
        try:
            ts = [ int(t) for t in t.split(':') ]
        except ValueError:
            ts = [ 0, 0, 0 ]
    else:
        now = datetime.datetime.now()
        ts = [ now.hour, now.minute, now.second ]

    return sum([ t * convert for t, convert in zip(ts, [3600, 60, 0 ])])


#makes your time comparison, returns true/false.
def is_time_between(first_time, second_time, compared_time = None):
    ct = split_time(compared_time)
    t1 = split_time(first_time)
    t2 = split_time(second_time)
    return t1 <= ct <= t2

# This is a quick test to return a true/false if the device is on.
def is_on(entity_id): return hass.states.get(entity_id) == 'on'

def getIntegerState(entity_id):
    try:
        return int(hass.states.get(entity_id))
    except ValueError:
        return 0

areWeUp             = ""
if hass.states.get("light.dining_table_lights").state == "on":
    areWeUp = "yes"
else:
    areWeUp = "no"

# Main floor bathroom.
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == "on":
   if is_time_between("07:00", "22:00"):
    if int(hass.states.get("sensor.illumination_158d000236a22f")) < 50:
        hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between("22:00", "07:00"):
        if areWeUp == "no":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '10', 'rgb_color': [255,0,0] })
        elif areWeUp == "yes":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_off', service_data={ 'entity_id': 'light.bathroom' })


# Conservatory
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == 'on': # Conservatory motion
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '10', 'kelvin': '4000' })

If I remove the time conditions in the script, then it does exactly what I expected; no errors. But with the time condition present, only the else statements work (if the motion is 'off'). If the motion is 'on', nothing happens (with either light), and I get the error above in the logs.

Try this

# Test scripts

#makes your time comparison, returns true/false.
def is_time_between(first_time, second_time, compared_time = None):
#quick way to convert a time string to an integer. Expects a time string formatted 00:00:00 or 00:00 or None
    def split_time(t):
        if t != None:
            try:
                ts = [ int(t) for t in t.split(':') ]
            except ValueError:
                ts = [ 0, 0, 0 ]
        else:
            now = datetime.datetime.now()
            ts = [ now.hour, now.minute, now.second ]
        return sum([ t * convert for t, convert in zip(ts, [3600, 60, 0 ])])
    
    ct = split_time(compared_time)
    t1 = split_time(first_time)
    t2 = split_time(second_time)
    return t1 <= ct <= t2

# This is a quick test to return a true/false if the device is on.
def is_on(entity_id): return hass.states.get(entity_id) == 'on'

def getIntegerState(entity_id):
    try:
        return int(hass.states.get(entity_id))
    except ValueError:
        return 0

areWeUp             = ""
if hass.states.get("light.dining_table_lights").state == "on":
    areWeUp = "yes"
else:
    areWeUp = "no"

# Main floor bathroom.
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == "on":
   if is_time_between("07:00", "22:00"):
    if int(hass.states.get("sensor.illumination_158d000236a22f")) < 50:
        hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between("22:00", "07:00"):
        if areWeUp == "no":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '10', 'rgb_color': [255,0,0] })
        elif areWeUp == "yes":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_off', service_data={ 'entity_id': 'light.bathroom' })


# Conservatory
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == 'on': # Conservatory motion
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '10', 'kelvin': '4000' })

Also, you can do this too to simplify some stuff:

if hass.states.get("light.dining_table_lights").state :
    areWeUp = "yes"
else:
    areWeUp = "no"

change to

awake = hass.states.get("light.dining_table_lights").state == "on"

then change this:

        if areWeUp == "no":

to

        if not awake:

or conversely

        if awake:

Cool, those last tips definitely help to clean up the main script a bit!

Hmmm… Using your code modified with split_time within is_time_between, HASS is still complaining:

Error executing script: name 'sum' is not defined
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "test_2.py", line 39, in <module>
  File "test_2.py", line 17, in is_time_between
  File "test_2.py", line 15, in split_time
NameError: name 'sum' is not defined

It’s referring to the line with sum, the line with ct = split_time(compared_time), and the first line with is_time_between.

i see petro is helping you on the right track.

i saw 1 thing that i think is worth a remark.

i saw you use several while loops after another.

the are all like while motion detected do what i placed in the loop.
the result from that is that the lights will keep going on als long as there is motion detected.
you dont want your lights to go on 10.000 times

you start your script by a motion detector and so it will go into 1 of those loops and stay there untill the motion detector goes to off.

you actualy want to use a normal if statement there. because the lights need to be turned of only once.
you trigger with a state change, so the motion going to off will trigger the script again and then the lights will go off, because you set that in the else.

1 other word of advice:
keep things small.
split up the actions for each motion detector and use a seperate script for each.
you wont lose overview, and its way more easy to debug.

@ReneTode Thanks, good suggestions!

I was really hoping I could trim a bit in my directories and automation.yaml with these python script :slight_smile: But I guess you’re right, it’ll probably be easier to manage/debug…

if you give the scripts the right name and keep them together, it will be way more easy to search back a motion detection from a certain place.

i saw you already started to give the sensor states readable names with these lines:

entrance_motion_state = hass.states.get("binary_sensor.motion_sensor_158d00023e3742")

so you could call the script entrance_motion etc.

i know people like to things together like all motion, all light, all switch, etc.
and then they get tangled up in trying to do things and scripts grow and grow and grow untill they have no clue where to look anymore.

i keep my actions based on events, and an event gets a logical name. easy to find back and easy to maintain.

Ugh, sum isn’t in the namespace? bah, I don’t know how anyone gets things done with python_scripts. I’d suggest moving to appdaemon.

Try this:

# Test scripts
#quick way to convert a time string to an integer. Expects a time string formatted 00:00:00 or 00:00 or None
def split_time(t):
    if t != None:
        try:
            ts = [ int(t) for t in t.split(':') ]
        except ValueError:
            ts = [ 0, 0, 0 ]   
     else:
        now = datetime.datetime.now()
        ts = [ now.hour, now.minute, now.second ]
    vt = 0
    for v in [ t * convert for t, convert in zip(ts, [3600, 60, 0 ])]:
        vt += v
    return vt

#makes your time comparison, returns true/false.
def is_time_between(first_time, second_time, compared_time = None):    
    ct = split_time(compared_time)
    t1 = split_time(first_time)
    t2 = split_time(second_time)
    return t1 <= ct <= t2

# This is a quick test to return a true/false if the device is on.
def is_on(entity_id): return hass.states.get(entity_id) == 'on'

def getIntegerState(entity_id):
    try:
        return int(hass.states.get(entity_id))
    except ValueError:
        return 0

areWeUp             = ""
if hass.states.get("light.dining_table_lights").state == "on":
    areWeUp = "yes"
else:
    areWeUp = "no"

# Main floor bathroom.
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == "on":
   if is_time_between("07:00", "22:00"):
    if int(hass.states.get("sensor.illumination_158d000236a22f")) < 50:
        hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '255', 'kelvin': '2700' })
    elif is_time_between("22:00", "07:00"):
        if areWeUp == "no":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '10', 'rgb_color': [255,0,0] })
        elif areWeUp == "yes":
            hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.bathroom', 'brightness': '200', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_off', service_data={ 'entity_id': 'light.bathroom' })


# Conservatory
if hass.states.get("binary_sensor.motion_sensor_158d000200d285").state == 'on': # Conservatory motion
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '255', 'kelvin': '2700' })
else:
    hass.services.call('light', 'turn_on', service_data={ 'entity_id': 'light.conservatory_couch', 'brightness': '10', 'kelvin': '4000' })
1 Like

I could do that. The only reason for using python scripts is that I intuitively thought it’d be better to use that sandboxed way. Especially since I don’t quite know what I’m doing yet.

Anyways, with you latest suggestion, it complains about split_time not being defined again. If I combine your last two suggestions like this:

#makes your time comparison, returns true/false.
def is_time_between(first_time, second_time, compared_time = None):
#quick way to convert a time string to an integer. Expects a time string formatted 00:00:00 or 00:00 or None
    def split_time(t):
        if t != None:
            try:
                ts = [ int(t) for t in t.split(':') ]
            except ValueError:
                ts = [ 0, 0, 0 ]
        else:
            now = datetime.datetime.now()
            ts = [ now.hour, now.minute, now.second ]
        vt = 0
        for v in [ t * convert for t, convert in zip(ts, [3600, 60, 0 ])]:
            vt += v
        return vt

    ct = split_time(compared_time)
    t1 = split_time(first_time)
    t2 = split_time(second_time)
    return t1 <= ct <= t2

I get a different error:

Error executing script: name '_inplacevar_' is not defined
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "test_2.py", line 44, in <module>
  File "test_2.py", line 21, in is_time_between
  File "test_2.py", line 18, in split_time
NameError: name '_inplacevar_' is not defined

I have no clue if Appdaemon is easier, but I already have it for dashboards, so I can give that a try instead.

@ReneTode That’s exactly the kind of advice that’s great now, before I get in so deep that it would take forever to sort out. Thanks!

Wow, it must not like zip(). Which is absurd. All the stuff would be much easier in appdaeamon because you can have the full python environment. Not this half-environment that python_scripts have.

Okay, okay, I’m convinced :smirk:. I’m reading the appdaemon docs (and @ReneTode’s intro) as we speak. Hopefully I’ll post a working script in… a… well… a while…

1 Like