I get that, but that’s why I mentioned that using homeassistant terms in contrast to how homeassistant uses them makes it more confusing.
If we consider that the word ‘trigger’ in both senses means “I want to react to this…” then @state_trigger makes perfect sense that we’re reacting to a state change, but the explanation of @state_trigger in the docs says this is a condition (which in homeassistant terms is “I don’t want to react if this is true”) which leads to immediate confusion.
To use your first example…
@state_trigger('binary_sensor.motion == "on"')
def turn_on_if_dark():
if binary_sensor.dark == 'on':
light.turn_on(entity_id='light.front_porch')
Seems like line 1 is the trigger and line 3 is the condition (in homeassistant parlance), and whilst that doesn’t necessarily translate (as the ‘condition’ is actually more like a templated action), I can get the concept of that easier now that I have understood that the word condition in the state_trigger was not a ‘homeassistant term’. The problem being that using that term in a component that is designed for homeassistant means that the reader expects it to mean what it means in homeassistant, which I think is where I started to get lost.
I hope that makes some sense.
Thanks for the bigger ‘real world’ example, that’s quite easy to follow and I mostly can see how it all slots together - although the @state_trigger ‘True or…’ has confused me. What is that checking?
That is a weird one that I don’t particularly care for, myself. The author explains it about half way through the state_trigger documentation.
In short, a state_trigger, as previously discussed, is both a trigger, and a condition – just like a template trigger. And, also like a template trigger, the code that powers each of these looks in the template (in Home Assistant) or the condition expression (in pyscript) to find which entities to look for changes in.
So we have, more simply, this:
@state_trigger('True or binary_sensor.dark')
Which is equivalent to this:
- alias: some automation
trigger:
platform: state
entity_id: binary_sensor.dark
We’re including “binary_sensor.dark” in the expression so that the code that looks for entities in that expression will see that it should evaluate the expression again whenever “binary_sensor.dark” changes. But, we don’t care what the state of “binary_sensor.dark” is. We want the function to run on any change. So we start the expression with “True” so that it will ALWAYS be “True” and therefore trigger the function.
It’s hacking looking, I agree. Pyscript is quite new so the syntax doesn’t have all the rough edges filed down yet. But I’ll be issuing a feature request shortly for a better way to represent this.
And you are spot on here. And if statement inside of the function is roughly equivalent to a “condition” in the “action” part of a home assistant automation. It’s more powerful than that, because an if can have an else. So it’s even more like the recently added “choose” functionality in a Home Assistant “action” / “script”.
It’s an abbreviated way of representing the same thing. I used state.get() on the others because they were set in a variable and state.get() is the way to get the state of an entity that is stored in a variable. Each of these values of dark will be the same:
dark = state.get('binary_sensor.dark')
dark = binary_sensor.dark
dark_sensor = 'binary_sensor.dark'
dark = state.get(dark_sensor)
For the curious, I had converted someone’s python_script to pyscript in this post. It was an exercise to help me understand the differences between the two. It also demonstrated how pyscript’s syntax is less verbose than python_script (with the same or better legibility … once you get accustomed to its syntax).
In the same spirit, I’ve converted swiftlyfalling’s pyscript example into a YAML automation. Once again it demonstrates how pyscript’s syntax allows for a less verbose end-result.
- alias: Climate Away Diff
trigger:
- platform: state
entity_id: climate.downstairs
- platform: state
entity_id: proximity.all
- platform: state
entity_id: input_number.desired_temperature
- platform: state
entity_id: input_number.away_temperature
- platform: homeassistant
event: start
action:
- service: input_number.set_value
data:
entity_id: input_number.away_temperature
value: >
{% set desired_temp_entity = 'input_number.desired_temperature' %}
{% set set_entity = 'input_number.away_temperature' %}
{% set cool_base = 3 %}
{% set heat_base = -4 %}
{% set max_diff = 10 %}
{% set min_diff = -10 %}
{% set cool_coeff = (1 / 10) %}
{% set heat_coeff = (-1 / 10) %}
{% set distance = states('proximity.all') | float %}
{% set desired_temp = states(desired_temp_entity) | float %}
{% set away_temp = states(set_entity) | float %}
{% set climate_state = states('climate.downstairs') %}
{% if climate_state == 'cool' %}
{% set diff = cool_base + (cool_coeff * distance) %}
{% elif climate_state == 'heat' %}
{% set diff = heat_base + (heat_coeff * distance) %}
{% else %}
{% set diff = 0 %}
{% endif %}
{% if diff > max_diff %}
{% diff = max_diff %}
{% endif %}
{% if diff < min_diff %}
{% diff = min_diff %}
{% endif %}
{{ desired_temp + round(diff) }}
It’s possible to streamline the template slightly but I chose not to in order to make it easier to compare syntax with the pyscript example.
NOTE
I must point out that, this is not 100% faithful to the pyscript example. I excluded the two logging calls because they would be completely separate service calls in a YAML automation.
That’s an advantage of using pyscript (and python_script): you can make any number of service calls within the body of the code. In a YAML automation, you are coding for each service call. So if the code (template) for one service call is needed for another call, you will have to repeat it.
Gah. I hate posts like this. It’s not bad enough that I’m using NodeRed, native automations, and AppDaemon… Now I have to throw this in the mix?! Shame on you @swiftlyfalling.
Seriously though, fantastic write up and I love that example that you put up. Now I have another tool to add to the automations wheelhouse.
Is anything really ever future proof? I mean, look at the changes to automations just between 0.114 and 0.116. Or the changes between AppDaemon3 and AppDaemon4.
Personally, I don’t ever bank on anything in technology to ever be 100% future proof.
you can use pyscript/apps/anything/__init__.py as long as you set it up as an app with YAML like this:
pyscript:
apps:
app_name: {}
From inside of an app you can import other files in that same app package.
You can import files in other directories. Though this may be limited to files in the pyscript/modules directory, I haven’t played with it much since the feature was added.
But, no, if you want to have pyscript autoload files in subdirectories of pyscript it doesn’t do that. Only the root pyscript/ directory. Make a Feature Request PR with your use case though, if it’s something you want. Craig is quite open to change requests.
And remember, it doesn’t check if it’s every been called before. It checks if there is a task actively running. Aside from very complex tasks and race conditions, this is likely only to happen if you are also calling task.sleep() or using some other async method that take time. Otherwise, generally, the task starts and stops so quickly it won’t still be running.
Well, hard to explain with my limited english.
Here’s my script. I commentent it in there.
Hope this makes sence, and thanks very much for your assistance.
@state_trigger("binary_sensor.motion_group == 'on'")
@state_active("input_boolean.night_time == 'on' and input_boolean.wz_desk_motion_light == 'on'")
def wz_desk_motion_light():
# The light should go on if it's darker than the setting in the input number
# But when the light comes on, it gets brighter than the input setting, so
# this will never pass the if statement again, and the light gets off
# even though there is still motion
if float(sensor.bh1750_illuminance) < float(input_number.motion_light_sonoff_01):
# or there's already a running task <- THIS is what i'm tryiing to do
task.unique("wz_desk_motion_light")
if light.schreibtisch_gu10 != "on":
x = sensor.random_color.split(',')[3]
y = sensor.random_color.split(',')[4]
light.turn_on(entity_id="light.schreibtisch_gu10",
brightness=140,
xy_color=[x,y])
task.sleep(float(input_number.motion_timer_sonoff_01))
light.turn_off(entity_id="light.schreibtisch_gu10")
I did this in yaml with a script and testet if that script was already running.