- Notifications
You must be signed in to change notification settings - Fork 58
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.