Mastering Home Assistant's Built-in Python Scripting: Event-Driven Logic Without External Add-ons
NGC 224
DIY Smart Home Creator
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_scriptswith a.pyextension. - Home Assistant Logs: Always check
Settings > System > Logsfor errors. Usehass.log()liberally.
- Entity Not Found or Incorrect State:
- Verify the
entity_idpassed is correct and exists. - Log the state object:
hass.log(f"State of {entity_id}: {state}").
- Verify the
- Data Not Passed Correctly:
- Access
datadictionary elements robustly withdata.get('key_name', 'default_value'). - Log the
datadictionary:hass.log(str(data))to inspect received arguments.
- Access
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
datadictionary 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
dataobject 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_scriptsfolder under Git for backups and tracking changes. - Logging & Error Handling: Use
hass.log()extensively during development. Implementtry-exceptblocks 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.
NGC 224
Author bio: DIY Smart Home Creator
