-
Notifications
You must be signed in to change notification settings - Fork 47
Comparing Pyscript to Home Assistant Automations
In Home Assistant:
- alias: some automation
trigger:
platform: state
entity_id: binary_sensor.test
to: 'on'
action:
- service: homeassistant.turn_on
entity_id: switch.test
In Pyscript:
@state_trigger('binary_sensor.test == "on"')
def turn_on():
switch.test.turn_on()
In Home Assistant:
- alias: some automation
trigger:
platform: state
entity_id: binary_sensor.test
to: 'on'
from: 'off'
action:
- service: homeassistant.turn_on
entity_id: switch.test
In Pyscript:
@state_trigger('binary_sensor.test == "on" and binary_sensor.test.old == "off"')
def turn_on():
switch.test.turn_on()
In Home Assistant:
- alias: some automation
trigger:
platform: state
entity_id: binary_sensor.test
action:
- service: homeassistant.turn_on
entity_id: switch.test
In Pyscript:
@state_trigger('binary_sensor.test')
def turn_on():
switch.test.turn_on()
In Home Assistant:
- alias: some automation
trigger:
platform: template
value_template: "{{ is_state('binary_sensor.test', 'on') }}"
action:
- service: homeassistant.turn_on
entity_id: switch.test
In Pyscript:
@state_trigger('binary_sensor.test == "on"')
def turn_on():
switch.test.turn_on()
In Home Assistant:
- alias: some automation
trigger:
platform: state
entity_id: binary_sensor.test
to: "on"
condition:
condition: template
value_template: "{{ is_state('input_boolean.test', 'on') }}"
action:
- service: homeassistant.turn_on
entity_id: switch.test
In Pyscript:
@state_trigger('binary_sensor.test == "on"')
@state_active('input_boolean.test == "on"')
def turn_on():
switch.test.turn_on()
The BuiltIn Alerts in Home Assistant require a Home Assistant restart to activate and don't have all the features I'd like. So, outside of basic cases, I often perform alerts with a Home Assistant Automation like this:
- alias: dishwasher_done_notification
mode: single
trigger:
- platform: template
value_template: >
{{
is_state('input_select.dishwasher_status', 'clean')
}}
- platform: homeassistant
event: start
- platform: event
event_type: automation_reloaded
condition:
- condition: template
value_template: >
{{
is_state('input_select.dishwasher_status', 'clean')
}}
variables:
start_time: "{{ as_timestamp(now()) }}"
action:
- repeat:
sequence:
- variables:
waited: "{{ ( (as_timestamp(now()) - start_time|float) / 60 )|round }}"
- choose:
conditions:
- condition: template
value_template: >
{{
repeat.first
}}
sequence:
- service: notify.house_notify_script
data:
message: "The dish washer is done. Please empty it."
default:
- service: notify.house_notify_script
data:
message: "The dish washer has been done for {{ waited }} minutes. I've told you {{ repeat.index }} times. Please empty it."
- delay:
minutes: 30
until:
- condition: template
value_template: >
{{
not is_state('input_select.dishwasher_status', 'clean')
}}
Every time I need a new alert like this, I cut and paste the automation and replace all the names, entities, messages, and conditions. If I, later, make an improvement on this automation, I have to change it in every place that I've used it already. Making it "reusable" by writing it as a Home Assistant Script is cumbersome because of the condition templates, and, even then, I'd still need an automation to trigger it.
Writing it in Pyscript, however, allows me to easily reuse the code. We can also take advantage of Pyscript's persistence feature to have an alert keep it's state through a Home Assistant Restart.
The reusable Pyscript might look like this. You'll notice I'm setting my alert parameters directly in the code. However, this could also be turned into an App with parameters being set in YAML. This Pyscript is about twice as long as the original Home Assistant automation. But, I'm using a verbose syntax to make the code more readable. And, remember, this is reusable and has persistence. If "lines of code" in an automation is important to you, you only need two of these to "break even".
import time
registered_triggers = []
def make_alert(config):
pass
log.info(f'Loading Alert {config["name"]}')
alert_entity = f'pyscript.alert_{config["name"]}'
state.persist(
alert_entity,
default_value="off",
default_attributes={
"count": 0,
"start_ts": 0
}
)
@task_unique(f'alert_{config["name"]}')
@state_trigger(f'True or {config["condition"]}')
@time_trigger('startup')
def alert():
condition_met = eval(config['condition'])
if not condition_met:
state.set(
alert_entity,
"off",
count=0,
start_ts=0
)
return
log.info(f'Alert {config["name"]} Started')
interval_seconds = config['interval'] * 60
try:
alert_count = int(state.get(f'{alert_entity}.count'))
alert_start_ts = int(state.get(f'{alert_entity}.start_ts'))
except:
alert_count = 0
alert_start_ts = 0
if alert_start_ts == 0:
alert_start_ts = round(time.time())
while condition_met:
alert_count = alert_count + 1
alert_time_seconds = time.time() - alert_start_ts
alert_time = round(alert_time_seconds / 60)
state.set(alert_entity, "on", start_ts=alert_start_ts, count=alert_count)
message_tpl = config['message']
if alert_count > 1 and "message_more" in config:
message_tpl = config['message_more']
message = eval(f"f'{message_tpl}'")
if message:
log.info(f'Sending Message: {message}')
service.call(
"notify",
config["notifier"],
message=message
)
wait = task.wait_until(
state_trigger=f'not ({config["condition"]})',
timeout = interval_seconds,
state_check_now=True
)
if wait['trigger_type'] == 'state':
condition_met = False
state.set(
alert_entity,
"off",
count=0,
start_ts=0
)
log.info(f'Alert {config["name"]} Finished')
registered_triggers.append(alert)
@time_trigger('startup')
def alert_startup():
make_alert({
"name": "dishwasher_done",
"condition": "input_select.dishwasher_status == 'clean'",
"interval": 30,
"notifier": "house_notify_script",
"message": "The dishwasher is done. Please empty it.",
"message_more": "This dishwasher has been done for {alert_time} minutes. I have told you {alert_count} times already. Please empty it."
})
You can see an even more complete version of alert
written as an app.