Mastering Home Assistant Scripting with pyscript: Bridging Python's Power with Smart Home Automations

Represent Mastering Home Assistant Scripting with pyscript: Bridging Python's Power with Smart Home Automations article
3m read

The Challenge: Beyond YAML's Limits

Home Assistant's YAML automations are excellent for many tasks, but complex logic, intricate data manipulation, or the need for advanced computation can quickly make them unwieldy. This is where pyscript shines, allowing you to embed full Python scripting directly within Home Assistant. With pyscript, you can leverage Python's extensive capabilities to create sophisticated, data-driven smart home automations.

Step-by-Step Setup: Integrating pyscript

1. Installation and Configuration

First, ensure HACS (Home Assistant Community Store) is installed. Then, go to HACS > Integrations, search for "pyscript", download it, and restart Home Assistant.

Add the following to your configuration.yaml:

# configuration.yaml
pyscript:
  allow_code_remote: true # Enable script reloads without HA restarts (dev)
  log_level: info         # Adjust for debugging (debug, info, warning, error)

allow_code_remote: true allows you to reload scripts via the pyscript.reload service (Developer Tools > Services), speeding up development. After changes, restart Home Assistant or call pyscript.reload.

2. Your First pyscript

Create a pyscript/ directory in your Home Assistant configuration folder. Inside, create hello_world.py:

# pyscript/hello_world.py

from datetime import datetime

@time_trigger('startup')
def hello_world_startup():
    log.info("pyscript: Hello from startup!")

@state_trigger("binary_sensor.front_door == 'on'")
def front_door_opened():
    log.info(f"Front door opened at {datetime.now().strftime('%H:%M:%S')}!")
    hass.services.call('persistent_notification', 'create', {
        'message': 'Front door just opened!',
        'title': 'Security Alert'
    })

This demonstrates @time_trigger for startup and @state_trigger for entity state changes. Save, reload pyscript, and check your Home Assistant logs (Settings > System > Logs) for output.

Troubleshooting Common Issues

  • Script Not Running: Verify correct file path (<config_dir>/pyscript/), YAML configuration, and ensure pyscript was reloaded/HA restarted. Check HA logs for errors.
  • Python Errors: Standard Python tracebacks are logged. Use an IDE for linting.
  • Service Call Issues: Confirm service domain, name, and payload in Developer Tools > Services before using in pyscript.
  • Debugging: Use log.info("Message") liberally. Set log_level: debug in configuration.yaml for verbose output.

Advanced Configuration and Optimization

Entity & Service Interaction

Use global objects state (e.g., state.get('sensor.temp')) to read entity states/attributes and hass.services.call('domain', 'service', {{'data': 'value'}}) to call services.

Code Organization

For modularity, split logic into multiple .py files and use standard Python import statements within the pyscript/ directory. Example:

# pyscript/my_utils.py
def adaptive_brightness(lux, min_lux):
    return int(200 * (min_lux / lux)) if lux < min_lux else 0

# pyscript/main.py
from my_utils import adaptive_brightness

@state_trigger('sensor.room_lux')
def control_light(new_value):
    brightness = adaptive_brightness(float(new_value), 50)
    if brightness > 0:
        hass.services.call('light', 'turn_on', {'entity_id': 'light.room', 'brightness': brightness})

Performance & State

  • Event-Driven: Always prefer @state_trigger, @time_trigger, @event_trigger over polling loops.
  • Persistent State: For state that persists across pyscript reloads, use Home Assistant's input_boolean, input_number, or input_text helpers instead of global Python variables.

Real-World Example: Dynamic Ambiance Control

Here's a pyscript for intelligent living room lighting, adjusting brightness and color temperature based on occupancy, ambient light, and time of day, respecting a manual override toggle.

Assumptions: binary_sensor.living_room_occupancy, sensor.living_room_lux_level, light.living_room_main, input_boolean.living_room_light_manual_mode.

# pyscript/dynamic_living_room_light.py

from datetime import datetime, time

@state_trigger(
    'binary_sensor.living_room_occupancy',
    'sensor.living_room_lux_level',
    'time',
    'input_boolean.living_room_light_manual_mode'
)
def manage_living_room_lights():
    # Manual override check
    if state.get('input_boolean.living_room_light_manual_mode') == 'on':
        log.info("Light in manual mode. Skipping automation.")
        return

    is_occupied = state.get('binary_sensor.living_room_occupancy') == 'on'
    current_lux = float(state.get('sensor.living_room_lux_level', default=0))
    light_entity = 'light.living_room_main'
    
    # Turn off if unoccupied
    if not is_occupied:
        if state.get(light_entity) == 'on':
            hass.services.call('light', 'turn_off', {'entity_id': light_entity, 'transition': 5})
        return
    
    # Occupied: calculate brightness and color temp
    now = datetime.now().time()
    
    # Time-based kelvin and base brightness
    if time(22, 0) <= now or now < time(6, 0): # Late night/early morning
        desired_kelvin, base_brightness_pct = 2200, 25
    elif time(6, 0) <= now < time(9, 0): # Morning
        desired_kelvin, base_brightness_pct = 2700, 50
    elif time(9, 0) <= now < time(17, 0): # Daytime
        desired_kelvin, base_brightness_pct = 4000, 75
    else: # Evening
        desired_kelvin, base_brightness_pct = 3000, 40

    # Lux-based brightness adjustment
    lux_reduction_factor = max(0, 1 - (min(current_lux, 200) / 200)) # Reduce if ambient light exists
    final_brightness_pct = max(10, min(100, int(base_brightness_pct * lux_reduction_factor)))

    # Update light if needed
    current_brightness_pct_ha = int(round((state.attr(light_entity, 'brightness') / 255) * 100)) if state.attr(light_entity, 'brightness') else 0
    current_kelvin = state.attr(light_entity, 'color_temp_kelvin')

    if final_brightness_pct > 0 and (
        state.get(light_entity) == 'off' or 
        abs(current_brightness_pct_ha - final_brightness_pct) > 5 or 
        abs(current_kelvin - desired_kelvin) > 100
    ):
        hass.services.call('light', 'turn_on', {
            'entity_id': light_entity,
            'brightness_pct': final_brightness_pct,
            'kelvin': desired_kelvin,
            'transition': 3
        })
    elif final_brightness_pct == 0 and state.get(light_entity) == 'on':
        hass.services.call('light', 'turn_off', {'entity_id': light_entity, 'transition': 3})

This script exemplifies how pyscript streamlines complex, multi-factor automations, offering superior control over brightness, color temperature, and state management.

Best Practices and Wrap-up

  • Security: pyscript has deep HA access. Only run trusted code.
  • Backup: Include your pyscript/ directory in HA backups (e.g., Google Drive Backup).
  • Version Control: Use Git for managing scripts, enabling easy tracking and rollbacks.
  • Code Clarity: Write readable code with comments and functions.
  • Testing: Develop and test in a non-production environment.
  • Error Handling: Use try-except blocks for robust scripts.

pyscript empowers Home Assistant users to move beyond declarative YAML, leveraging Python's full power for truly custom, intelligent, and responsive smart home automations.

Avatar picture of NGC 224
Written by:

NGC 224

Author bio: DIY Smart Home Creator

There are no comments yet
loading...