Mastering Home Assistant's Built-in Python Scripting: Event-Driven Logic Without External Add-ons

Represent Mastering Home Assistant's Built-in Python Scripting: Event-Driven Logic Without External Add-ons article
5m read

Are your Home Assistant automations hitting a wall? While YAML-based automations are powerful for many tasks, complex logic, intricate data manipulation, or highly dynamic scenarios often demand more flexibility. You might find yourself wanting to leverage Python's full power without relying on external add-ons like pyscript (a HACS integration) or AppDaemon, which add their own layers of complexity and dependencies.

This is where Home Assistant's often-underestimated built-in python_script integration shines. It provides a secure, efficient way to execute custom Python code directly within your Home Assistant instance, allowing you to craft sophisticated, event-driven automations. This guide will walk you through setting up and mastering python_script to unlock advanced control and create a truly intelligent smart home, offering precise, replicable results for tech enthusiasts and practical homeowners alike.

Getting Started: Enabling and Using python_script

Enabling the python_script integration is straightforward. It’s a core component, meaning no HACS installation is required.

1. Enable the Integration:

Add the following line to your configuration.yaml file:

# configuration.yaml
python_script:

After saving, restart Home Assistant for the changes to take effect. You should then see python_script listed under Settings > Integrations.

2. Create the python_scripts Folder:

In your Home Assistant configuration directory (where configuration.yaml resides), create a new folder named python_scripts. This is where all your custom Python files will live.

3. Write Your First Script:

Let's create a simple script to toggle a light and send a persistent notification. Inside your python_scripts folder, create a file named simple_light_toggle.py:

# python_scripts/simple_light_toggle.py
# Access the Home Assistant 'hass' object

entity_id = data.get('entity_id')

if entity_id is not None:
    state = hass.states.get(entity_id)
    
    if state is not None:
        current_state = state.state
        
        # Log for debugging
        hass.log(f"Script called for {entity_id}. Current state: {current_state}")
        
        # Toggle the light
        if current_state == 'on':
            hass.services.call('light', 'turn_off', {'entity_id': entity_id}, False)
            message = f"{entity_id} turned OFF by script."
        else:
            hass.services.call('light', 'turn_on', {'entity_id': entity_id}, False)
            message = f"{entity_id} turned ON by script."
        
        # Send a persistent notification
        hass.services.call('persistent_notification', 'create', {
            'title': 'Script Action',
            'message': message,
            'notification_id': f'script_toggle_{entity_id.replace(".", "_")}'
        }, False)
    else:
        hass.log(f"Error: Entity {entity_id} not found.")
else:
    hass.log("Error: 'entity_id' not provided to the script.")

Key elements of a python_script:

  • hass: Global object for Home Assistant state, services, and logging.
  • data: Dictionary containing data passed from the triggering automation.
  • hass.states.get('entity_id'): Retrieves an entity's state object.
  • hass.services.call('domain', 'service', {'data_payload'}, blocking=False): Calls a Home Assistant service.
  • hass.log(message): Writes messages to the Home Assistant log for debugging.

4. Trigger the Script from an Automation:

Create an automation that calls your Python script. The script name (without `.py` extension) becomes the service name under the python_script domain.

# automations.yaml
- id: '1678901234567'
  alias: Toggle Living Room Light via Python Script
  description: Toggles a light and sends a notification using a custom Python script.
  trigger:
    - platform: state
      entity_id: input_button.toggle_light_script # Create an Input Button helper for testing
      to: 'on' # Trigger when the button is pressed
  action:
    - service: python_script.simple_light_toggle # The script name is the service name
      data:
        entity_id: light.living_room_lamp # Pass data to the script
  mode: single

Replace light.living_room_lamp with an actual light entity. You can create an input_button helper in Home Assistant for easy testing.

Troubleshooting Common Issues

When working with python_script, anticipate a few hiccups:

  • Script Not Executing:
    • Syntax Errors: Python is strict. Check your scripts for basic syntax errors.
    • Incorrect Service Call: Ensure the service is python_script.your_script_name.
    • File Not Found: Verify the Python file is in python_scripts with a .py extension.
    • Home Assistant Logs: Always check Settings > System > Logs for errors. Use hass.log() liberally.
  • Entity Not Found or Incorrect State:
    • Verify the entity_id passed is correct and exists.
    • Log the state object: hass.log(f"State of {entity_id}: {state}").
  • Data Not Passed Correctly:
    • Access data dictionary elements robustly with data.get('key_name', 'default_value').
    • Log the data dictionary: hass.log(str(data)) to inspect received arguments.

Advanced Configuration and Optimization

python_script allows powerful interactions with Home Assistant.

  • Accessing More Home Assistant Data: Use hass.states.all() for all states.
  • Utilizing Script Arguments: Design scripts to accept parameters via the data dictionary for reusability.
  • Performance Considerations: Avoid long-running scripts, heavy computations, or blocking network I/O, as scripts run within Home Assistant's main process. Most smart home automations are well within limits.
  • Security Best Practices:
    • Review Code: Scrutinize all scripts, especially external ones, as they have significant access.
    • Input Validation: Sanitize data from the data object before use, particularly for sensitive operations.

Real-World Example: Dynamic Light Brightness Based on Ambient Light and Time of Day

Let's create a sophisticated script that adjusts a light's brightness dynamically, responding to ambient light levels and time.

# python_scripts/dynamic_light_brightness.py

entity_id = data.get('entity_id')
light_sensor_id = data.get('light_sensor_id') # e.g., sensor.living_room_light_level
min_brightness = data.get('min_brightness', 10) # Default 10%
max_brightness = data.get('max_brightness', 100) # Default 100%
lux_threshold_bright = data.get('lux_threshold_bright', 100)
lux_threshold_dim = data.get('lux_threshold_dim', 30)
day_start = data.get('day_start', '07:00:00')
night_start = data.get('night_start', '20:00:00')

if not entity_id or not light_sensor_id:
    hass.log("Error: 'entity_id' or 'light_sensor_id' not provided to script.")
else:
    light_state = hass.states.get(entity_id)
    sensor_state = hass.states.get(light_sensor_id)
    current_time_state = hass.states.get('sensor.time') # Requires time_date integration
    
    if not light_state or not sensor_state or not current_time_state:
        hass.log(f"Error: Could not get state for {entity_id}, {light_sensor_id}, or sensor.time.")
    else:
        try:
            current_lux = float(sensor_state.state)
            current_time = current_time_state.state
            
            is_day = day_start <= current_time < night_start
            
            target_brightness_pct = 0
            if is_day:
                if current_lux < lux_threshold_dim: target_brightness_pct = 70
                elif current_lux < lux_threshold_bright: target_brightness_pct = 50
                else: target_brightness_pct = min_brightness
            else: # Night time
                if current_lux < lux_threshold_dim: target_brightness_pct = 40
                else: target_brightness_pct = min_brightness
            
            target_brightness_pct = max(min_brightness, min(max_brightness, target_brightness_pct))
            target_brightness = int(target_brightness_pct / 100 * 255)
            
            if light_state.state == 'on' or target_brightness_pct > 0:
                hass.log(f"Setting {entity_id} to brightness {target_brightness} ({target_brightness_pct}%) based on {current_lux} lux and time.")
                hass.services.call('light', 'turn_on', {
                    'entity_id': entity_id,
                    'brightness': target_brightness,
                    'transition': 5
                }, False)
            elif light_state.state == 'on' and target_brightness_pct == 0:
                 hass.log(f"Turning off {entity_id} as target brightness is 0%.")
                 hass.services.call('light', 'turn_off', {'entity_id': entity_id}, False)

        except ValueError:
            hass.log(f"Error: Could not convert light sensor state '{sensor_state.state}' to a number.")
        except Exception as e:
            hass.log(f"An unexpected error occurred: {e}")

This script requires a light entity, an ambient light sensor, and the time_date integration enabled (by adding time_date: to your configuration.yaml). This makes sensor.time available.

Trigger this script with an automation:

# automations.yaml
- id: '1678901234568'
  alias: Adjust Living Room Light Dynamically
  description: Adjusts light brightness based on ambient light and time using Python script.
  trigger:
    - platform: state
      entity_id: sensor.living_room_light_level # Trigger when ambient light changes
    - platform: time_pattern
      minutes: "/5" # Trigger every 5 minutes to catch time changes
    - platform: homeassistant
      event: start # Also run on HA start
  action:
    - service: python_script.dynamic_light_brightness
      data:
        entity_id: light.living_room_lamp
        light_sensor_id: sensor.living_room_light_level
        min_brightness: 5
        max_brightness: 90
        lux_threshold_bright: 150
        lux_threshold_dim: 50
        day_start: '06:30:00'
        night_start: '21:00:00'
  mode: parallel # Allow multiple executions if triggers overlap

This automation triggers on ambient light changes, every 5 minutes, or on Home Assistant startup. mode: parallel ensures responsiveness.

Best Practices and Wrap-up

python_script is invaluable when YAML automations become overly complex due to conditional logic or data transformations.

  • Version Control: Keep your python_scripts folder under Git for backups and tracking changes.
  • Logging & Error Handling: Use hass.log() extensively during development. Implement try-except blocks for robust operation.
  • Clear Documentation: Add comments to explain script purpose, arguments, and complex logic.
  • When to Choose python_script:
    • You need custom logic too complex for YAML.
    • You want to avoid external dependencies for simplicity.
    • You're comfortable with Python for rapid prototyping of specific functions.
    • Your script's execution time is short (seconds).

For extensive projects requiring external libraries, long-running processes, or deep asynchronous capabilities, consider AppDaemon or the pyscript HACS integration. For visual flow-based programming, Node-RED remains an excellent choice.

By mastering Home Assistant's built-in python_script integration, you gain a versatile tool to craft highly customized and intelligent automations, transforming your smart home into a truly responsive and adaptive environment. This approach bridges the gap between simple YAML and advanced external scripting, offering a robust solution for complex, event-driven smart home logic.

Avatar picture of NGC 224
Written by:

NGC 224

Author bio: DIY Smart Home Creator

There are no comments yet
loading...