Mastering Home Assistant Scripts: Building Reusable, Parameterized Automation Logic

Represent Mastering Home Assistant Scripts: Building Reusable, Parameterized Automation Logic article
6m read

Introduction: The Pitfall of Repetitive Automations

As your Home Assistant setup grows, you inevitably encounter a common challenge: repetitive automation logic. You might find yourself copying and pasting similar sequences of actions across multiple automations – perhaps a notification routine that sends alerts to different devices based on the trigger, or a series of steps to prepare your home for sleep. This practice, while functional initially, quickly leads to configuration bloat, maintenance nightmares, and a higher probability of introducing errors when updates are needed. If you've ever thought, "There has to be a better way to reuse this block of actions," you're ready to master Home Assistant's scripting capabilities.

Home Assistant scripts are powerful, reusable sequences of actions that can be called from automations, other scripts, or even directly from the UI. Unlike automations which are triggered by specific events or states, scripts are manually invoked and can accept parameters, making them incredibly versatile. By leveraging scripts, you can encapsulate complex logic, centralize common tasks, and drastically reduce the redundancy in your configuration, leading to a more maintainable, scalable, and robust smart home system.

Step-by-Step Setup: Creating Your First Parameterized Script

Let's begin by creating a simple, parameterized script that sends a customizable notification. This will illustrate how to define a script and pass data to it.

1. Define the Script in scripts.yaml

First, ensure you have a scripts.yaml file included in your configuration.yaml:

# configuration.yaml
script:
  !include scripts.yaml

Now, open scripts.yaml and add your first script:

# scripts.yaml
notify_with_title_and_message:
  alias: Send a Dynamic Notification
  sequence:
    - service: notify.mobile_app_your_device # Replace with your notification service
      data:
        title: "{{ title | default('Home Assistant Notification') }}"
        message: "{{ message | default('No message provided.') }}"
        data:
          tag: "{{ tag | default('home_assistant') }}"
          color: "{{ color | default('blue') }}"

Explanation:

  • notify_with_title_and_message: This is the unique ID of your script.
  • alias: A user-friendly name visible in the UI.
  • sequence: The list of actions the script will perform.
  • service: notify.mobile_app_your_device: The notification service call. Adjust this to your specific notification service (e.g., notify.telegram, notify.hass_mobile_app_iphone).
  • data:: This section uses Jinja2 templating to access parameters passed to the script.
  • {{ title | default('Home Assistant Notification') }}: This expects a title variable. If not provided, it defaults to 'Home Assistant Notification'. The same applies to message, tag, and color.

2. Call the Script from an Automation

Now, let's create an automation to trigger this script and pass it some data. Imagine you want to send a notification when your front door opens.

# automations.yaml
- id: '1678901234567'
  alias: Front Door Opened Alert
  trigger:
    - platform: state
      entity_id: binary_sensor.front_door
      to: 'on'
  action:
    - service: script.notify_with_title_and_message
      data:
        title: "Security Alert!"
        message: "The front door was opened."
        tag: "security_alert"
        color: "red"

Explanation:

  • service: script.notify_with_title_and_message: This is how you invoke a script. The service domain is script, and the service name is the script's ID.
  • data:: This is where you pass parameters to your script. The keys (title, message, tag, color) correspond to the variables expected by the script.

Troubleshooting Common Script Issues

Scripts, while powerful, can be tricky. Here are common issues and how to troubleshoot them:

1. Script Not Running / No Notification

  • Check Home Assistant Logs: Go to Settings > System > Logs. Look for errors related to script or notify services.
  • Verify Service Call: Ensure the service: script.your_script_id in your automation matches the script ID exactly.
  • Test Script Directly: Go to Developer Tools > Services, select your script (e.g., script.notify_with_title_and_message), and manually enter data variables (e.g., title: Test, message: Manual Test) to see if it runs.

2. Parameters Not Received / Templating Errors

  • Variable Mismatch: Double-check that the variable names in the data: section of your automation exactly match the variable names used in your script's Jinja2 templates (e.g., {{ title }}).
  • Default Values: If you're using | default('...'), temporary remove the default to see if the template is truly evaluating to None.
  • Developer Tools > Template: Use the Template editor under Developer Tools to test your Jinja2 expressions. For instance, to simulate the script, you can temporarily define variables like this:
    {% set title = 'My Custom Title' %}
    {% set message = 'Hello from the template editor!' %}
    Title: {{ title }}
    Message: {{ message }}
    
  • Quote Strings: Ensure all string values in YAML are properly quoted, especially if they contain special characters.

Advanced Config / Optimization: Dynamic Script Invocation and Complex Logic

1. Dynamic Entity IDs and Lists

You're not limited to simple strings for parameters. You can pass full entity IDs, lists of entities, or complex objects.

# scripts.yaml
toggle_multiple_lights:
  alias: Toggle a List of Lights
  sequence:
    - service: light.toggle
      target:
        entity_id: "{{ lights_to_toggle | default([]) }}"

# automation.yaml (calling the script)
- id: '1678901234568'
  alias: Evening Light Toggle
  trigger:
    - platform: time
      at: "18:00:00"
  action:
    - service: script.toggle_multiple_lights
      data:
        lights_to_toggle:
          - light.living_room_lamp
          - light.kitchen_counter_lights

2. Conditional Logic (choose, if/then) within Scripts

Scripts can contain advanced branching logic, making them truly powerful decision-makers.

# scripts.yaml
handle_door_event:
  alias: Process Door Event
  sequence:
    - variables:
        door_entity: "{{ entity_id }}"
        event_type: "{{ to_state }}"
    - choose:
        - conditions:
            - "{{ event_type == 'on' }}"
          sequence:
            - service: script.notify_with_title_and_message
              data:
                title: "Door Open!"
                message: "{{ door_entity }} was opened."
                color: "red"
            - service: light.turn_on
              target:
                entity_id: light.hallway_light
        - conditions:
            - "{{ event_type == 'off' }}"
          sequence:
            - service: script.notify_with_title_and_message
              data:
                title: "Door Closed"
                message: "{{ door_entity }} was closed."
                color: "green"
            - delay: "00:00:30"
            - service: light.turn_off
              target:
                entity_id: light.hallway_light
      default: []

# automation.yaml (calling the script from any door sensor)
- id: '1678901234569'
  alias: Monitor All Doors with Script
  trigger:
    - platform: state
      entity_id:
        - binary_sensor.front_door
        - binary_sensor.back_door
        - binary_sensor.garage_door
  action:
    - service: script.handle_door_event
      data:
        entity_id: "{{ trigger.entity_id }}"
        to_state: "{{ trigger.to_state.state }}"

This example demonstrates a single script handling both 'open' and 'close' events for multiple door sensors, significantly reducing automation redundancy.

3. Using repeat for Iteration

Scripts can iterate through lists or perform actions a set number of times.

# scripts.yaml
pulse_light:
  alias: Pulse a Light for Notifications
  sequence:
    - repeat:
        count: "{{ count | default(3) }}"
        sequence:
          - service: light.turn_on
            target:
              entity_id: "{{ light_entity_id }}"
            data:
              brightness: 255
              color_name: "{{ color | default('red') }}"
          - delay: "00:00:01"
          - service: light.turn_off
            target:
              entity_id: "{{ light_entity_id }}"
          - delay: "00:00:01"

Real-World Example: A Comprehensive "Good Night" Routine

Let's build a robust "Good Night" script that prepares your home for the evening, encompassing various actions based on parameters.

# scripts.yaml
good_night_routine:
  alias: Execute Good Night Routine
  sequence:
    - variables:
        arm_alarm: "{{ arm_alarm | default(true) }}" # Default to arming
        lights_off_area: "{{ lights_off_area | default('all') }}" # 'all', 'bedrooms', 'living_room'
        set_hvac_mode: "{{ hvac_mode | default('auto') }}"
        hvac_temp: "{{ target_temp | default(20) }}"
        notify_if_issues: "{{ notify_issues | default(true) }}"

    - service: system_log.write
      data:
        message: "Starting Good Night Routine with arm_alarm={{ arm_alarm }}, lights_off_area={{ lights_off_area }}"
        level: info

    # Step 1: Turn off lights based on area
    - choose:
        - conditions: "{{ lights_off_area == 'all' or lights_off_area == 'living_room' }}"
          sequence:
            - service: light.turn_off
              target:
                area_id: living_room
                # Can also use entity_id list
        - conditions: "{{ lights_off_area == 'all' or lights_off_area == 'bedrooms' }}"
          sequence:
            - service: light.turn_off
              target:
                area_id: bedrooms
      default: []

    # Step 2: Set HVAC mode and temperature
    - service: climate.set_hvac_mode
      target:
        entity_id: climate.thermostat
      data:
        hvac_mode: "{{ set_hvac_mode }}"

    - service: climate.set_temperature
      target:
        entity_id: climate.thermostat
      data:
        temperature: "{{ hvac_temp }}"

    # Step 3: Arm security system if requested
    - if: "{{ arm_alarm }}"
      then:
        - service: alarm_control_panel.alarm_arm_home
          target:
            entity_id: alarm_control_panel.home_alarm
        - delay: "00:00:05" # Give alarm time to arm
        - if: "{{ is_state('alarm_control_panel.home_alarm', 'armed_home') }}"
          then:
            - service: script.notify_with_title_and_message
              data:
                title: "Good Night!"
                message: "Alarm armed. Home secure."
                color: "green"
          else:
            - if: "{{ notify_if_issues }}"
              then:
                - service: script.notify_with_title_and_message
                  data:
                    title: "Alarm Arming Failed!"
                    message: "Check for open doors/windows."
                    color: "orange"

    # Step 4: Final checks and notifications
    - if:
        - conditions:
            - "{{ is_state('binary_sensor.garage_door', 'on') }}"
            - "{{ notify_if_issues }}"
      then:
        - service: script.notify_with_title_and_message
          data:
            title: "Reminder!"
            message: "Garage door is still open!"
            color: "yellow"

    - service: script.notify_with_title_and_message
      data:
        title: "Routine Complete"
        message: "Good night routine finished. Sweet dreams!"
        color: "blue"

Now, you can trigger this script from various sources:

  • Bedside Button:
  • # automation.yaml
    - id: 'bedside_button_good_night'
      alias: Bedside Button - Good Night
      trigger:
        - platform: state
          entity_id: sensor.bedside_button_action
          to: 'single'
      action:
        - service: script.good_night_routine
          data:
            arm_alarm: true
            lights_off_area: 'all'
            target_temp: 19
            notify_issues: true
    
  • Time-Based Automation (Late Night):
  • # automation.yaml
    - id: 'late_night_good_night'
      alias: Automatic Late Night Shutdown
      trigger:
        - platform: time
          at: "01:00:00"
      action:
        - service: script.good_night_routine
          data:
            arm_alarm: false # Don't arm if everyone is asleep
            lights_off_area: 'all'
            target_temp: 18
            hvac_mode: 'heat'
            notify_issues: false # Don't wake people up for minor issues
    

This demonstrates how a single, well-crafted script can serve multiple automation needs by simply varying the input parameters, making your Home Assistant configuration incredibly flexible and easy to manage.

Best Practices / Wrap-up

To ensure your script-driven automations are scalable, reliable, and secure, consider these best practices:

1. Organization and Naming Conventions

  • Descriptive IDs: Use clear, lowercase, underscore-separated IDs for your scripts (e.g., notify_critical_alert).
  • Aliases: Always provide a human-readable alias for easy identification in the UI.
  • Folder Structure: For large setups, consider breaking scripts.yaml into a directory structure (e.g., scripts/notifications.yaml, scripts/routines.yaml) and using !include_dir_list or !include_dir_named.

2. Documentation and Comments

  • Inline Comments: Use # to add comments within your scripts, explaining complex logic or variable usage.
  • Script Descriptions: Consider adding a description field to your script for more extensive documentation, especially for parameters.

3. Robustness and Error Handling

  • Default Values: Always use | default('...') in your templates to provide fallback values if a parameter isn't passed, preventing template errors.
  • System Logs: Leverage system_log.write within critical scripts to log their execution and passed parameters, aiding debugging.
  • Conditions and Choices: Use choose and if/then statements to handle different scenarios gracefully and prevent unintended actions.

4. Version Control (Git)

Treat your Home Assistant configuration, especially scripts and automations, as code. Use Git to track changes, revert to previous versions, and collaborate. This is invaluable for complex setups.

5. Testing and Validation

  • Developer Tools > Services: Manually call scripts from Developer Tools to test different parameter combinations.
  • Dedicated Test Automations: Create temporary automations specifically to trigger scripts with various inputs during development.

By embracing Home Assistant scripts, you move beyond basic automations to build a truly intelligent, efficient, and easily maintainable smart home. Start refactoring your repetitive actions into parameterized scripts today, and experience the power of modular automation.

Avatar picture of NGC 224
Written by:

NGC 224

Author bio: DIY Smart Home Creator

There are no comments yet
loading...