Home Automation

Home Automation

Tips and Tricks for Home Automation

04 Jan 2021

PyScript Modules for Repeatable Automations

The Home Assistant world is abuzz with the idea of Blueprints: repeatable automation logic with different inputs.

While this feature is incredibly useful in Home Automation, most programming languages have “functions”, which is essentially the same idea (minus all of the UI bits that also make Blueprints a great addition). Since PyScript is based on Python, building repeatable automation logic is already included.

Let’s start with a simple “motion activated light” automation in PyScript:

1
2
3
4
5
6
7
8
9
@state_trigger('binary_sensor.motion == "on"')
@task_unique('motion_lights')
def handle_lights():
    light.overhead.turn_on()
    task.wait_until(
        state_trigger='binary_sensor.motion == "off"')
    )
    task.sleep(300)
    light.overhead.turn_off()

If you aren’t familiar with the features being used above, checkout the PyScript Documentation and get acquainted.

Just like with a Home Assistant Blueprint, the first step is determining what the inputs will be. In this case, we have a motion_sensor, a light_entity, and a delay_seconds. So, we’ll adjust our code to take these parameters by replacing each use of an entity_id with the variable that holds that information. We’ll do the same for the number of seconds to delay after motion stops.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@state_trigger(f'{motion_sensor} == "on"')
@task_unique(f'motion_lights_{motion_sensor}')
def handle_lights():
    homeassistant.turn_on(
        entity_id=light_entity
    )
    task.wait_until(
        state_trigger=f'{motion_sensor} == "off"')
    )
    task.sleep(delay_seconds)
    homeassistant.turn_off(
        entity_id=light_entity
    )

Now, we need to wrap this in a function of its own.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def create_motion_activated_lights(
        motion_sensor,
        light_entity,
        delay_seconds
    ):
    @state_trigger(f'{motion_sensor} == "on"')
    @task_unique(f'motion_lights_{motion_sensor}')
    def handle_lights():
        nonlocal motion_sensor, light_entity, delay_seconds

        homeassistant.turn_on(
            entity_id=light_entity
        )
        task.wait_until(
            state_trigger=f'{motion_sensor} == "off"'
        )
        task.sleep(delay_seconds)
        homeassistant.turn_off(
            entity_id=light_entity
        )

    return handle_lights

Making this a pyscript module is easy. Create a directory called modules in your pyscript directory if you don’t already have one. Then create a new file in the modules directory to hold our new module. Let’s call it lighting.py. Just enter the above code into this file.

Finally, in any pyscript file that you would like to create a “motion activated lights” automation, you can use this module, like so:

1
2
3
4
5
6
7
import lighting

light_function = lighting.create_motion_activated_lights(
    motion_sensor='binary_sensor.motion',
    light_entity='light.overhead',
    delay_seconds=300
)

But perhaps you’d also like to be able to create an automation like this using only YAML. We’re going to use a pyscript “app” for that.

First, we’ll create a file at /pyscript/apps/motion_activated_lights.py. The following code goes in that file. It will go through the YAML configuration for this app, and use our module to create the automations. We’ll use a global variable called TRIGGERS to store the triggers in so that Pyscript will register them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import lighting

TRIGGERS = []

@time_trigger('startup')
def startup():
    for config in pyscript.config['apps']['motion_activated_lights']:
        TRIGGERS.append(
            lighting.create_motion_activated_lights(**config)
        )

Now, with our app ready to go, we only need to add the YAML configuration.

1
2
3
4
5
6
7
8
9
pyscript:
    apps:
        motion_activated_lights:
            - motion_sensor: binary_sensor.living_motion
              light_entity: light.living_overhead
              delay_seconds: 60
            - motion_sensor: binary_sensor.kitchen_motion
              light_entity: light.kitchen_overhead
              delay_seconds: 120

In just a few, repeatable steps we’ve turned a single automation into an automation that is usable in other pyscripts as well as usable with only YAML to configure it.