Add homeassistant skill in unpacked format

- Add complete homeassistant skill source to skills/ directory
- Includes all scripts, references, and automation templates
- Matches format of other skills in repository
This commit is contained in:
Felix Zösch
2025-12-16 12:49:56 +01:00
parent 43f3a0d0b4
commit 9be64a0696
14 changed files with 2393 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
---
name: homeassistant
description: "Comprehensive Home Assistant smart home automation toolkit. Use when users need help with: (1) Creating or editing automations in YAML, (2) Controlling or querying devices via the Home Assistant API, (3) Writing and validating configuration files, (4) Troubleshooting automation or configuration issues, (5) Understanding Home Assistant concepts like triggers, conditions, actions, (6) Integrating with smart home devices and services. Trigger for queries about home automation, YAML automation configs, entity states, service calls, or debugging Home Assistant setups."
---
# Home Assistant
## Overview
This skill enables Claude to work with Home Assistant, the open-source home automation platform. It provides tools for creating automations, controlling devices via API, validating configurations, and troubleshooting common issues.
## Core Capabilities
### 1. Automation Creation and Editing
Create, modify, and troubleshoot Home Assistant automations in YAML format.
**Key concepts:**
- **Triggers** - What starts the automation (state change, time, sun event, etc.)
- **Conditions** - Optional checks that must pass before actions run
- **Actions** - What the automation does (call services, delays, notifications, etc.)
**Quick example:**
```yaml
automation:
- alias: "Motion Light"
trigger:
- trigger: state
entity_id: binary_sensor.motion
to: "on"
action:
- action: light.turn_on
target:
entity_id: light.hallway
```
**For detailed automation syntax:** See `references/automation_reference.md`
**For common patterns:** See `references/common_patterns.md`
**Pre-built templates:** Available in `assets/automation_templates/`:
- `motion_light.yaml` - Motion-activated lighting
- `time_based.yaml` - Time-scheduled actions
- `climate_control.yaml` - Temperature-based climate control
- `presence_detection.yaml` - Arrival/departure automation
- `notification.yaml` - Alert and notification patterns
### 2. Device Control via API
Query entity states and control devices through Home Assistant's REST or WebSocket API.
**Prerequisites:**
- Home Assistant URL (e.g., `http://homeassistant.local:8123`)
- Long-lived access token (from Profile page in HA)
**Common operations:**
**List all entities:**
```bash
python3 scripts/list_entities.py --url http://homeassistant.local:8123 --token YOUR_TOKEN
```
**Get specific entity state:**
```bash
python3 scripts/api_client.py --url http://homeassistant.local:8123 --token YOUR_TOKEN get-state light.living_room
```
**Control a device:**
```bash
python3 scripts/api_client.py --url http://homeassistant.local:8123 --token YOUR_TOKEN call-service light turn_on --entity light.kitchen --data '{"brightness": 255}'
```
**List available services:**
```bash
python3 scripts/list_services.py --url http://homeassistant.local:8123 --token YOUR_TOKEN
```
**Environment variables:**
Set `HA_URL` and `HA_TOKEN` to avoid passing credentials each time:
```bash
export HA_URL=http://homeassistant.local:8123
export HA_TOKEN=your_token_here
```
**For complete API documentation:** See `references/api_reference.md`
### 3. Configuration Validation
Validate YAML configuration files for syntax errors and common issues.
**Validate any YAML file:**
```bash
python3 scripts/validate_yaml.py configuration.yaml
```
**Validate automation configuration:**
```bash
python3 scripts/validate_yaml.py automations.yaml --type automation
```
**Common issues detected:**
- Tab characters (Home Assistant requires spaces)
- Missing required fields (e.g., `trigger` in automations)
- Incorrect YAML syntax
- Unquoted boolean states
- Structural problems
**Verbose output:**
```bash
python3 scripts/validate_yaml.py automations.yaml --type automation --verbose
```
### 4. Troubleshooting and Debugging
Diagnose and fix common Home Assistant issues.
**Common problems:**
**YAML syntax errors:**
- Tab characters: Use spaces only (2 spaces per indent level)
- Boolean states: Quote them: `state: "on"` not `state: on`
- Indentation: Must be exact (2 spaces per level)
**Automations not triggering:**
1. Check automation is enabled
2. Verify entity IDs are correct
3. Check trigger conditions (quoted states, numeric values)
4. Review automation trace in Home Assistant UI
**Template errors:**
- Use default filters: `{{ states('sensor.test') | default('unknown') }}`
- Check entity exists before accessing
**API errors:**
- 401 Unauthorized: Verify access token is valid
- 404 Not Found: Check entity ID spelling and existence
- Service call fails: List services to verify parameters
**For complete troubleshooting guide:** See `references/troubleshooting.md`
## Workflow Examples
### Creating a New Automation
1. **Understand the requirement:**
- What should trigger it? (motion, time, state change, etc.)
- What conditions should apply? (time of day, presence, etc.)
- What should it do? (turn on lights, send notification, etc.)
2. **Choose a starting point:**
- Use pre-built template from `assets/automation_templates/`
- Or reference common pattern from `references/common_patterns.md`
- Or build from scratch using `references/automation_reference.md`
3. **Customize for specific entities:**
- Replace placeholder entity IDs (e.g., `REPLACE_WITH_MOTION_SENSOR`)
- List available entities: `python3 scripts/list_entities.py`
- Adjust parameters (brightness, temperature, delays, etc.)
4. **Validate the configuration:**
```bash
python3 scripts/validate_yaml.py your_automation.yaml --type automation
```
5. **Test and iterate:**
- Add to Home Assistant
- Use automation trace feature to debug
- Refine based on behavior
### Controlling Devices from API
1. **Discover available entities:**
```bash
python3 scripts/list_entities.py --domain light
```
2. **Check current state:**
```bash
python3 scripts/api_client.py get-state light.living_room
```
3. **List available services for domain:**
```bash
python3 scripts/list_services.py --domain light
```
4. **Call service:**
```bash
python3 scripts/api_client.py call-service light turn_on --entity light.living_room --data '{"brightness": 128}'
```
### Debugging a Failed Automation
1. **Check for YAML errors:**
```bash
python3 scripts/validate_yaml.py automations.yaml --type automation
```
2. **Review error patterns in troubleshooting guide:**
- See `references/troubleshooting.md`
- Common issues: tabs, boolean states, indentation
3. **Check Home Assistant logs:**
```bash
python3 scripts/api_client.py get-error-log
```
4. **Verify entity IDs exist:**
```bash
python3 scripts/list_entities.py | grep "problem_entity"
```
5. **Test with simplified version:**
- Remove conditions temporarily
- Add debug notification action
- Use automation trace in HA UI
## Important Notes
**YAML Syntax Rules:**
- Use 2 spaces for indentation (never tabs)
- Quote state values: `"on"`, `"off"`, `"home"`, `"not_home"`
- Entity IDs are case-sensitive
- All states are strings (use `| int` or `| float` for numeric operations)
**API Authentication:**
- Requires long-lived access token from Profile page
- Pass via `Authorization: Bearer TOKEN` header
- Or set `HA_TOKEN` environment variable
**Templates:**
- Use Jinja2 syntax: `{{ states('entity_id') }}`
- Add default values: `{{ states('sensor.test') | default('unknown') }}`
- Access attributes: `{{ state_attr('entity_id', 'attribute') }}`
**Service Calls:**
- Domain and service required: `light.turn_on`, `climate.set_temperature`
- Target entities via `target` or `entity_id` field
- Additional parameters go in `data` field
## Resources
### Scripts
- `api_client.py` - Home Assistant REST API client for controlling devices and querying states
- `validate_yaml.py` - Validate YAML configurations and automations
- `list_entities.py` - List all entities with filtering options
- `list_services.py` - List available services by domain
All scripts support `--help` flag for usage information.
### References
Load these as needed for detailed information:
- `api_reference.md` - Complete REST and WebSocket API documentation
- `automation_reference.md` - Full automation YAML structure and syntax
- `common_patterns.md` - Real-world automation examples and patterns
- `troubleshooting.md` - Comprehensive debugging guide for common issues
### Assets
- `automation_templates/` - Pre-built YAML templates for common automation types
- Copy and customize templates by replacing placeholder entity IDs
- Each template includes comments explaining configuration options

View File

@@ -0,0 +1,32 @@
automation:
- alias: "Temperature-Based Climate Control"
id: climate_control_template
description: "Adjust climate control based on temperature"
trigger:
- trigger: numeric_state
entity_id: sensor.REPLACE_WITH_TEMPERATURE_SENSOR
below: 19 # Adjust threshold temperature
condition:
# Optional: Only when someone is home
- condition: state
entity_id: person.REPLACE_WITH_PERSON
state: "home"
action:
- action: climate.set_hvac_mode
target:
entity_id: climate.REPLACE_WITH_CLIMATE_DEVICE
data:
hvac_mode: heat # heat, cool, heat_cool, off, etc.
- action: climate.set_temperature
target:
entity_id: climate.REPLACE_WITH_CLIMATE_DEVICE
data:
temperature: 21 # Target temperature
- action: notify.notify
data:
message: "Heating turned on. Temperature: {{ states('sensor.REPLACE_WITH_TEMPERATURE_SENSOR') }}°C"

View File

@@ -0,0 +1,39 @@
automation:
- alias: "Motion-Activated Light"
id: motion_light_template
description: "Turn on lights when motion detected, turn off after motion stops"
trigger:
- trigger: state
entity_id: binary_sensor.REPLACE_WITH_MOTION_SENSOR
to: "on"
condition:
# Optional: Only trigger during dark hours
- condition: sun
after: sunset
before: sunrise
action:
# Turn on the light
- action: light.turn_on
target:
entity_id: light.REPLACE_WITH_LIGHT
data:
brightness: 255 # Adjust brightness (0-255)
# Wait for motion to stop
- wait_for_trigger:
- trigger: state
entity_id: binary_sensor.REPLACE_WITH_MOTION_SENSOR
to: "off"
for:
minutes: 5 # Adjust delay before turning off
timeout:
hours: 2 # Maximum time to wait
continue_on_timeout: true
# Turn off the light
- action: light.turn_off
target:
entity_id: light.REPLACE_WITH_LIGHT

View File

@@ -0,0 +1,34 @@
automation:
- alias: "Notification Automation"
id: notification_template
description: "Send notifications based on events or conditions"
trigger:
- trigger: state
entity_id: binary_sensor.REPLACE_WITH_SENSOR
to: "on"
condition:
# Optional: Only notify during specific times
- condition: time
after: "08:00:00"
before: "22:00:00"
action:
- action: notify.notify # Default notification service
data:
message: >
{{ trigger.to_state.attributes.friendly_name }} is now {{ trigger.to_state.state }}
at {{ now().strftime('%H:%M:%S') }}
title: "Home Assistant Alert"
# Optional: Send to specific mobile device
# - action: notify.mobile_app_REPLACE_WITH_DEVICE_NAME
# data:
# message: "Custom notification message"
# title: "Alert"
# data:
# # Optional: Add action buttons
# actions:
# - action: "ACTION_NAME"
# title: "Action Button"

View File

@@ -0,0 +1,57 @@
automation:
- alias: "Presence-Based Automation"
id: presence_detection_template
description: "React to people arriving or leaving home"
trigger:
- trigger: state
entity_id: person.REPLACE_WITH_PERSON
from: "not_home"
to: "home"
id: arriving
- trigger: state
entity_id: person.REPLACE_WITH_PERSON
from: "home"
to: "not_home"
id: leaving
action:
- choose:
# When arriving home
- conditions:
- condition: trigger
id: arriving
sequence:
- action: light.turn_on
target:
area_id: entrance
- action: climate.set_temperature
target:
entity_id: climate.REPLACE_WITH_CLIMATE_DEVICE
data:
temperature: 22
- action: notify.mobile_app_REPLACE_WITH_DEVICE
data:
message: "Welcome home!"
# When leaving home
- conditions:
- condition: trigger
id: leaving
sequence:
- action: light.turn_off
target:
entity_id: all
- action: climate.set_hvac_mode
target:
entity_id: climate.REPLACE_WITH_CLIMATE_DEVICE
data:
hvac_mode: "off"
- action: notify.mobile_app_REPLACE_WITH_DEVICE
data:
message: "Goodbye! Home secured."

View File

@@ -0,0 +1,31 @@
automation:
- alias: "Time-Based Automation"
id: time_based_template
description: "Execute actions at a specific time"
trigger:
- trigger: time
at: "07:00:00" # Replace with desired time (24-hour format)
condition:
# Optional: Only on specific days
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
action:
# Add your actions here
- action: light.turn_on
target:
entity_id: light.REPLACE_WITH_LIGHT
data:
brightness: 128
- action: notify.notify
data:
message: "Good morning! Time-based automation triggered."
title: "Morning Routine"

View File

@@ -0,0 +1,284 @@
# Home Assistant API Reference
## REST API
### Authentication
All API requests require a Long-Lived Access Token passed in the Authorization header:
```
Authorization: Bearer YOUR_TOKEN_HERE
```
Obtain tokens from: `http://YOUR_HA_URL:8123/profile`
### Base URL
```
http://YOUR_HA_URL:8123/api/
```
### Key Endpoints
#### Get All Entity States
```http
GET /api/states
```
Returns array of all entities with their current states, attributes, and timestamps.
#### Get Specific Entity State
```http
GET /api/states/<entity_id>
```
Example: `/api/states/light.living_room`
Returns single entity object with state, attributes, last_changed, last_updated.
#### Call a Service
```http
POST /api/services/<domain>/<service>
```
Example: `/api/services/light/turn_on`
Request body (optional):
```json
{
"entity_id": "light.living_room",
"brightness": 255,
"color_name": "blue"
}
```
Returns array of entity states that were changed by the service call.
#### List All Services
```http
GET /api/services
```
Returns object with domains as keys, each containing available services with descriptions and field definitions.
#### Get Configuration
```http
GET /api/config
```
Returns Home Assistant configuration including version, location, unit system, time zone, etc.
#### Get Error Log
```http
GET /api/error_log
```
Returns text content of the error log.
### Response Codes
- `200` - Success (existing resource)
- `201` - Created (new resource)
- `400` - Bad Request
- `401` - Unauthorized
- `404` - Not Found
- `405` - Method Not Allowed
## WebSocket API
### Connection
Connect to: `ws://YOUR_HA_URL:8123/api/websocket`
### Authentication Flow
1. Client connects
2. Server sends `auth_required` message:
```json
{
"type": "auth_required",
"ha_version": "2024.1.0"
}
```
3. Client sends `auth` message:
```json
{
"type": "auth",
"access_token": "YOUR_TOKEN_HERE"
}
```
4. Server responds with either:
```json
{"type": "auth_ok"}
```
or
```json
{
"type": "auth_invalid",
"message": "Invalid access token"
}
```
### Message Format
All messages use JSON. Command phase messages require an `id` field for request/response correlation.
### Common Commands
#### Get All States
```json
{
"id": 1,
"type": "get_states"
}
```
Response:
```json
{
"id": 1,
"type": "result",
"success": true,
"result": [
{
"entity_id": "light.living_room",
"state": "on",
"attributes": {...},
"last_changed": "2024-01-15T10:30:00+00:00",
"last_updated": "2024-01-15T10:30:00+00:00"
}
]
}
```
#### Call Service
```json
{
"id": 2,
"type": "call_service",
"domain": "light",
"service": "turn_on",
"target": {
"entity_id": "light.kitchen"
},
"service_data": {
"brightness": 255,
"color_name": "warm_white"
},
"return_response": true
}
```
Response:
```json
{
"id": 2,
"type": "result",
"success": true,
"result": {
"context": {
"id": "326ef27d19415c60c492fe330945f954",
"parent_id": null,
"user_id": "31ddb597e03147118cf8d2f8fbea5553"
},
"response": null
}
}
```
#### Subscribe to Events
Subscribe to state changes:
```json
{
"id": 3,
"type": "subscribe_events",
"event_type": "state_changed"
}
```
Server confirms subscription:
```json
{
"id": 3,
"type": "result",
"success": true
}
```
Then sends events as they occur:
```json
{
"id": 3,
"type": "event",
"event": {
"event_type": "state_changed",
"data": {
"entity_id": "sensor.temperature",
"old_state": {...},
"new_state": {...}
}
}
}
```
#### Unsubscribe from Events
```json
{
"id": 4,
"type": "unsubscribe_events",
"subscription": 3
}
```
### Error Responses
```json
{
"id": 5,
"type": "result",
"success": false,
"error": {
"code": "invalid_format",
"message": "Message incorrectly formatted"
}
}
```
## Common Entity Domains
- `light` - Lights
- `switch` - Switches
- `sensor` - Sensors (read-only)
- `binary_sensor` - Binary sensors (on/off, open/closed)
- `climate` - Climate control (thermostats, AC)
- `cover` - Covers (blinds, garage doors)
- `fan` - Fans
- `media_player` - Media players
- `camera` - Cameras
- `lock` - Door locks
- `alarm_control_panel` - Alarm systems
- `vacuum` - Vacuum cleaners
- `automation` - Automations themselves
- `script` - Scripts
- `scene` - Scenes
## Common Service Patterns
Most domains support these services:
- `turn_on` - Turn entity on
- `turn_off` - Turn entity off
- `toggle` - Toggle entity state
Domain-specific services may include additional parameters like `brightness`, `color_temp`, `position`, etc.

View File

@@ -0,0 +1,389 @@
# Home Assistant Automation Reference
## Complete Automation Structure
```yaml
automation:
- alias: "Friendly Name for Display"
id: unique_identifier_123
description: "Optional description of what this automation does"
initial_state: true
mode: single
variables:
my_variable: "some_value"
trigger:
- trigger: state
entity_id: binary_sensor.motion_sensor
to: "on"
condition:
- condition: time
after: "sunset"
before: "sunrise"
action:
- action: light.turn_on
target:
entity_id: light.living_room
data:
brightness: 255
```
## Required and Optional Fields
### Required
- `trigger` - At least one trigger that initiates the automation
### Optional (Recommended)
- `alias` - Friendly name displayed in UI
- `id` - Unique identifier (essential for UI editor)
- `description` - Documentation of automation purpose
- `action` - Actions to execute (automation does nothing without actions)
### Optional (Advanced)
- `condition` - Conditions that must be true to execute actions
- `mode` - Execution behavior: `single`, `restart`, `queued`, `parallel`
- `max` - Maximum concurrent executions (for `queued`/`parallel` modes)
- `variables` - Variables available throughout automation
- `initial_state` - Whether enabled at startup (`true`/`false`)
- `trace` - Debug trace configuration
## Triggers
Triggers define when an automation runs.
### State Trigger
Fires when entity changes state:
```yaml
trigger:
- trigger: state
entity_id: binary_sensor.motion_detector
from: "off"
to: "on"
for:
seconds: 5 # Optional: state must persist for duration
```
### Time Trigger
Fires at specific time:
```yaml
trigger:
- trigger: time
at: "15:30:00"
```
### Sun Trigger
Fires at sunrise/sunset:
```yaml
trigger:
- trigger: sun
event: sunset
offset: "-00:30:00" # 30 minutes before sunset
```
### Numeric State Trigger
Fires when sensor value crosses threshold:
```yaml
trigger:
- trigger: numeric_state
entity_id: sensor.temperature
above: 25
below: 30
```
### Template Trigger
Fires when template evaluates to true:
```yaml
trigger:
- trigger: template
value_template: "{{ states('sensor.temperature') | float > 25 }}"
```
### Event Trigger
Fires on specific event:
```yaml
trigger:
- trigger: event
event_type: custom_event
event_data:
action: button_pressed
```
### Zone Trigger
Fires when device enters/leaves zone:
```yaml
trigger:
- trigger: zone
entity_id: person.john
zone: zone.home
event: enter # or 'leave'
```
### Multiple Triggers (OR Logic)
List multiple triggers - any one firing will run the automation:
```yaml
trigger:
- trigger: state
entity_id: binary_sensor.motion_1
to: "on"
- trigger: state
entity_id: binary_sensor.motion_2
to: "on"
- trigger: time
at: "18:00:00"
```
## Conditions
Conditions must be true for actions to execute.
### State Condition
```yaml
condition:
- condition: state
entity_id: light.living_room
state: "off"
```
### Numeric State Condition
```yaml
condition:
- condition: numeric_state
entity_id: sensor.temperature
below: 20
```
### Time Condition
```yaml
condition:
- condition: time
after: "16:00:00"
before: "23:00:00"
weekday:
- mon
- tue
- wed
- thu
- fri
```
### Sun Condition
```yaml
condition:
- condition: sun
after: sunset
before: sunrise
```
### Template Condition
```yaml
condition:
- condition: template
value_template: "{{ states('sensor.battery') | int > 20 }}"
```
### Zone Condition
```yaml
condition:
- condition: zone
entity_id: person.john
zone: zone.home
```
### Multiple Conditions (AND Logic)
All conditions must be true:
```yaml
condition:
- condition: state
entity_id: person.john
state: "home"
- condition: time
after: "sunset"
```
### OR Conditions
```yaml
condition:
- condition: or
conditions:
- condition: state
entity_id: person.john
state: "home"
- condition: state
entity_id: person.mary
state: "home"
```
## Actions
Actions define what the automation does.
### Call Service
```yaml
action:
- action: light.turn_on
target:
entity_id: light.kitchen
data:
brightness: 200
color_temp: 400
```
### Multiple Entities
```yaml
action:
- action: light.turn_on
target:
entity_id:
- light.kitchen
- light.living_room
- light.bedroom
```
### Delay
```yaml
action:
- delay:
seconds: 30
```
### Wait for Trigger
```yaml
action:
- wait_for_trigger:
- trigger: state
entity_id: binary_sensor.motion
to: "off"
timeout:
minutes: 5
```
### Conditional Action
```yaml
action:
- condition: state
entity_id: sun.sun
state: "below_horizon"
- action: light.turn_on
target:
entity_id: light.porch
```
### Choose (If-Then-Else)
```yaml
action:
- choose:
- conditions:
- condition: state
entity_id: sensor.weather
state: "sunny"
sequence:
- action: cover.open_cover
target:
entity_id: cover.blinds
- conditions:
- condition: state
entity_id: sensor.weather
state: "rainy"
sequence:
- action: cover.close_cover
target:
entity_id: cover.blinds
default:
- action: notify.notify
data:
message: "Weather is {{ states('sensor.weather') }}"
```
### Repeat
```yaml
action:
- repeat:
count: 3
sequence:
- action: light.toggle
target:
entity_id: light.hallway
- delay:
seconds: 1
```
## Templates
Use Jinja2 templates for dynamic values:
```yaml
action:
- action: notify.notify
data:
message: "Temperature is {{ states('sensor.temperature') }}°C"
title: "Weather at {{ now().strftime('%H:%M') }}"
```
### Common Template Functions
- `{{ states('entity_id') }}` - Get entity state
- `{{ state_attr('entity_id', 'attribute') }}` - Get entity attribute
- `{{ now() }}` - Current datetime
- `{{ trigger.entity_id }}` - Triggering entity ID
- `{{ trigger.to_state.state }}` - New state value
- `{{ trigger.from_state.state }}` - Old state value
## Execution Modes
- `single` (default) - Only one instance runs at a time. New triggers ignored while running.
- `restart` - Restart automation from beginning when triggered while running.
- `queued` - Queue additional triggers, run them sequentially.
- `parallel` - Allow multiple instances to run simultaneously.
```yaml
mode: parallel
max: 5 # Maximum 5 concurrent executions
```
## Best Practices
1. Always include `id` field for UI editor compatibility
2. Use meaningful `alias` names for easy identification
3. Add `description` to document complex automations
4. Prefer specific triggers over broad templates when possible
5. Use conditions to prevent unnecessary actions
6. Test automations with trace mode enabled
7. Keep automation logic simple - split complex logic into multiple automations or scripts
8. Quote state values: `state: "on"` not `state: on`
9. Use 2-space indentation, no tabs
10. Validate YAML before deploying

View File

@@ -0,0 +1,379 @@
# Common Home Assistant Automation Patterns
## Motion-Activated Lighting
Turn on lights when motion detected, turn off after no motion:
```yaml
automation:
- alias: "Motion Light - Hallway"
id: motion_light_hallway
trigger:
- trigger: state
entity_id: binary_sensor.hallway_motion
to: "on"
condition:
- condition: state
entity_id: sun.sun
state: "below_horizon"
action:
- action: light.turn_on
target:
entity_id: light.hallway
- wait_for_trigger:
- trigger: state
entity_id: binary_sensor.hallway_motion
to: "off"
for:
minutes: 5
- action: light.turn_off
target:
entity_id: light.hallway
```
## Time-Based Automation
Execute actions at specific times:
```yaml
automation:
- alias: "Morning Routine"
id: morning_routine
trigger:
- trigger: time
at: "07:00:00"
condition:
- condition: state
entity_id: binary_sensor.workday
state: "on"
action:
- action: light.turn_on
target:
entity_id: light.bedroom
data:
brightness: 50
- action: cover.open_cover
target:
entity_id: cover.bedroom_blinds
- action: climate.set_temperature
target:
entity_id: climate.bedroom
data:
temperature: 21
```
## Sunset/Sunrise Automation
React to sun position:
```yaml
automation:
- alias: "Lights at Sunset"
id: lights_at_sunset
trigger:
- trigger: sun
event: sunset
offset: "-00:30:00" # 30 min before sunset
action:
- action: light.turn_on
target:
area_id: living_room
data:
brightness: 180
```
## Presence Detection
React to people arriving/leaving:
```yaml
automation:
- alias: "Welcome Home"
id: welcome_home
trigger:
- trigger: state
entity_id: person.john
from: "not_home"
to: "home"
action:
- action: light.turn_on
target:
area_id: entrance
- action: climate.set_temperature
target:
entity_id: climate.living_room
data:
temperature: 22
- action: notify.mobile_app
data:
message: "Welcome home, John!"
```
## Temperature-Based Climate Control
Adjust heating/cooling based on temperature:
```yaml
automation:
- alias: "Auto Climate Control"
id: auto_climate_control
trigger:
- trigger: numeric_state
entity_id: sensor.living_room_temperature
below: 19
condition:
- condition: state
entity_id: person.john
state: "home"
action:
- action: climate.set_hvac_mode
target:
entity_id: climate.living_room
data:
hvac_mode: heat
- action: climate.set_temperature
target:
entity_id: climate.living_room
data:
temperature: 21
```
## Door/Window Alert
Notify when doors/windows left open:
```yaml
automation:
- alias: "Window Open Alert"
id: window_open_alert
trigger:
- trigger: state
entity_id: binary_sensor.living_room_window
to: "on"
for:
minutes: 30
condition:
- condition: numeric_state
entity_id: sensor.outdoor_temperature
below: 10
action:
- action: notify.notify
data:
message: "Living room window has been open for 30 minutes and it's {{ states('sensor.outdoor_temperature') }}°C outside"
title: "Window Alert"
```
## Low Battery Notification
Alert when device batteries are low:
```yaml
automation:
- alias: "Low Battery Alert"
id: low_battery_alert
trigger:
- trigger: numeric_state
entity_id: sensor.motion_sensor_battery
below: 20
action:
- action: notify.notify
data:
message: "{{ trigger.to_state.attributes.friendly_name }} battery is at {{ trigger.to_state.state }}%"
title: "Low Battery"
```
## Conditional Lighting by Time
Different brightness based on time of day:
```yaml
automation:
- alias: "Motion Light with Time-Based Brightness"
id: motion_light_time_based
trigger:
- trigger: state
entity_id: binary_sensor.kitchen_motion
to: "on"
action:
- choose:
- conditions:
- condition: time
after: "22:00:00"
before: "06:00:00"
sequence:
- action: light.turn_on
target:
entity_id: light.kitchen
data:
brightness: 30
- conditions:
- condition: time
after: "06:00:00"
before: "22:00:00"
sequence:
- action: light.turn_on
target:
entity_id: light.kitchen
data:
brightness: 255
```
## Vacation Mode
Randomize lights when away:
```yaml
automation:
- alias: "Vacation Mode - Random Lights"
id: vacation_mode_lights
trigger:
- trigger: time_pattern
hours: "/1" # Every hour
condition:
- condition: state
entity_id: input_boolean.vacation_mode
state: "on"
- condition: sun
after: sunset
before: sunrise
action:
- action: light.turn_on
target:
entity_id: "{{ ['light.living_room', 'light.bedroom', 'light.kitchen'] | random }}"
- delay:
minutes: "{{ range(30, 120) | random }}"
- action: light.turn_off
target:
entity_id: all
```
## Media Player Automation
Pause media when phone rings:
```yaml
automation:
- alias: "Pause Media on Phone Call"
id: pause_media_phone_call
trigger:
- trigger: state
entity_id: sensor.phone_state
to: "ringing"
action:
- action: media_player.media_pause
target:
entity_id: all
```
## Garden Watering Schedule
Water garden based on weather and schedule:
```yaml
automation:
- alias: "Garden Watering"
id: garden_watering
trigger:
- trigger: time
at: "06:00:00"
condition:
- condition: state
entity_id: sensor.weather_forecast
state: "sunny"
- condition: numeric_state
entity_id: sensor.outdoor_temperature
above: 20
- condition: numeric_state
entity_id: sensor.soil_moisture
below: 30
action:
- action: switch.turn_on
target:
entity_id: switch.garden_sprinkler
- delay:
minutes: 30
- action: switch.turn_off
target:
entity_id: switch.garden_sprinkler
```
## Reminder Notifications
Remind to perform tasks:
```yaml
automation:
- alias: "Trash Day Reminder"
id: trash_day_reminder
trigger:
- trigger: time
at: "20:00:00"
condition:
- condition: time
weekday:
- sun
action:
- action: notify.notify
data:
message: "Don't forget to put out the trash tonight!"
title: "Trash Day Tomorrow"
```
## Smart Doorbell
Announce doorbell press and record:
```yaml
automation:
- alias: "Doorbell Press"
id: doorbell_press
trigger:
- trigger: state
entity_id: binary_sensor.doorbell
to: "on"
action:
- action: media_player.play_media
target:
entity_id: media_player.home_speaker
data:
media_content_id: "local/doorbell.mp3"
media_content_type: "music"
- action: camera.snapshot
target:
entity_id: camera.front_door
data:
filename: "/config/www/snapshots/doorbell_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg"
- action: notify.mobile_app
data:
message: "Someone is at the door"
data:
image: "/local/snapshots/doorbell_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg"
```
## Energy Saving
Turn off devices when nobody home:
```yaml
automation:
- alias: "Energy Saving - Away Mode"
id: energy_saving_away
trigger:
- trigger: state
entity_id: group.all_persons
to: "not_home"
for:
minutes: 15
action:
- action: light.turn_off
target:
entity_id: all
- action: climate.set_hvac_mode
target:
entity_id: all
data:
hvac_mode: "off"
- action: media_player.turn_off
target:
entity_id: all
```

View File

@@ -0,0 +1,342 @@
# Home Assistant Troubleshooting Guide
## YAML Configuration Errors
### Tab Characters Error
**Error:** `found character '\t' that cannot start any token`
**Cause:** YAML file contains tab characters
**Solution:**
- Replace all tabs with spaces (use 2 spaces per indentation level)
- Configure editor to use spaces instead of tabs
- Run validation script: `python3 validate_yaml.py configuration.yaml`
### Boolean State Errors
**Error:** `not a valid value for dictionary value`
**Cause:** Unquoted boolean state values like `on`, `off`, `yes`, `no`
**Solution:**
- Quote state values: `state: "on"` not `state: on`
- YAML interprets `on`, `yes`, `true` as boolean `true`
- Quote them to use as strings: `"on"`, `"off"`, `"yes"`, `"no"`
### Duplicate Keys
**Error:** Automation not working as expected
**Cause:** Duplicate keys in YAML - only last value is used
**Solution:**
```yaml
# Wrong - duplicate 'action' keys
action: light.turn_on
action: notify.notify
# Correct - use list
action:
- action: light.turn_on
- action: notify.notify
```
### Indentation Errors
**Error:** `mapping values are not allowed here`
**Cause:** Incorrect indentation
**Solution:**
- Use exactly 2 spaces per indentation level
- Check that list items align properly with `-`
- Validate structure:
```yaml
# Correct
automation:
- alias: "Test"
trigger:
- trigger: state
entity_id: light.test
```
## Automation Issues
### Automation Not Triggering
**Check:**
1. Automation is enabled (check Developer Tools > States > `automation.your_automation`)
2. Trigger conditions are correct:
- Entity ID exists and is spelled correctly
- State values are quoted: `to: "on"` not `to: on`
- For numeric triggers, ensure sensor provides numeric values
3. Check automation trace (Settings > Automations > automation > Traces)
**Debug:**
```yaml
# Add notification to verify trigger fires
action:
- action: notify.notify
data:
message: "Automation triggered at {{ now() }}"
# ... rest of actions
```
### Automation Runs But Actions Don't Execute
**Check:**
1. Conditions - if any condition fails, actions won't run
2. Entity IDs in actions are correct
3. Service parameters are valid
4. Check logs for errors: Settings > System > Logs
**Debug:**
```yaml
# Temporarily remove conditions to test actions
# condition:
# - condition: state
# entity_id: sun.sun
# state: "below_horizon"
```
### Template Errors
**Error:** `TemplateError: UndefinedError`
**Cause:** Template references non-existent entity or attribute
**Solution:**
```yaml
# Use default filter for safety
{{ states('sensor.missing') | default('unknown') }}
{{ state_attr('sensor.test', 'missing_attr') | default(0) }}
# Check if entity exists
{% if states('sensor.test') != 'unknown' %}
{{ states('sensor.test') }}
{% endif %}
```
### "While" Loop Not Working
**Cause:** Using `for` when you meant duration, not iteration
**Solution:**
```yaml
# Wrong - 'for' iterates over value
trigger:
- trigger: state
entity_id: motion_sensor
to: "on"
for: 5 # This is wrong
# Correct - specify duration
trigger:
- trigger: state
entity_id: binary_sensor.motion
to: "on"
for:
seconds: 5
```
## API Issues
### 401 Unauthorized
**Cause:** Invalid or missing access token
**Solution:**
1. Generate new long-lived token at `http://YOUR_HA_URL:8123/profile`
2. Verify token in Authorization header: `Authorization: Bearer YOUR_TOKEN`
3. Check token hasn't been deleted in Home Assistant
### 404 Not Found - Entity
**Cause:** Entity ID doesn't exist or is misspelled
**Solution:**
1. List all entities: `python3 list_entities.py --url YOUR_URL --token YOUR_TOKEN`
2. Check exact entity ID spelling (case-sensitive)
3. Verify entity is not disabled in Home Assistant
### Service Call Fails
**Error:** `Service not found` or `Invalid service data`
**Solution:**
1. List available services: `python3 list_services.py --url YOUR_URL --token YOUR_TOKEN`
2. Verify service parameters match requirements
3. Check service still exists (some integrations change service names)
**Example:**
```python
# Get service details
python3 list_services.py --url YOUR_URL --token YOUR_TOKEN --domain light
# Shows required/optional fields for each service
```
## Integration Issues
### Integration Not Loading
**Check:**
1. Configuration syntax: `configuration.yaml` has correct format
2. Required parameters are provided
3. Check error log: `http://YOUR_HA_URL:8123/config/logs`
**Solution:**
1. Validate configuration: Configuration > Server Controls > Check Configuration
2. Restart Home Assistant: Configuration > Server Controls > Restart
3. Check integration documentation for required setup
### Device Unavailable
**Check:**
1. Device is powered on and connected to network
2. Integration is configured correctly
3. Check integration's specific requirements (API keys, credentials, etc.)
**Debug:**
```yaml
# Check device state in Developer Tools
# Developer Tools > States > search for entity_id
# Status shows as 'unavailable' or 'unknown'
```
## Performance Issues
### Slow Response Times
**Causes:**
- Too many entities or automations
- Database growing too large
- Inefficient automations (templates evaluating frequently)
**Solutions:**
1. Purge old data: Configuration > System > Storage
2. Optimize database: `recorder` configuration in `configuration.yaml`:
```yaml
recorder:
purge_keep_days: 7
exclude:
domains:
- automation
- updater
entity_globs:
- sensor.weather_*
```
3. Reduce template sensor update frequency
4. Use `homeassistant.reload_core_config` instead of full restart when possible
### Automations Firing Too Often
**Cause:** State changes triggering repeatedly
**Solution:**
```yaml
# Add 'for' duration to prevent rapid triggers
trigger:
- trigger: state
entity_id: sensor.temperature
for:
minutes: 5
# Or add condition to limit frequency
condition:
- condition: template
value_template: >
{{ (now() - state_attr('automation.this_automation', 'last_triggered') | default(as_datetime(0), true)).total_seconds() > 300 }}
```
## Network Issues
### Can't Connect to Home Assistant
**Check:**
1. Home Assistant is running: `systemctl status home-assistant` (if using systemd)
2. Firewall allows port 8123
3. Using correct URL (http not https, or vice versa)
4. Network connectivity between client and server
**Solutions:**
```bash
# Test connection
curl http://YOUR_HA_URL:8123/api/
# Should return: {"message": "API running."}
# Check from Home Assistant server
curl http://localhost:8123/api/
```
### SSL/Certificate Errors
**Cause:** Using HTTPS with self-signed or invalid certificate
**Solution:**
1. Use HTTP for local testing
2. For production, use valid SSL certificate (Let's Encrypt via DuckDNS)
3. For testing with self-signed cert, disable verification (not recommended for production)
## Debugging Tools
### Check Automation Trace
1. Go to Settings > Automations & Scenes
2. Click automation
3. Click "Traces" tab
4. Shows each trigger, condition check, and action execution
### Developer Tools
**Template Tab:**
- Test templates before using in automations
- See real-time template evaluation
**States Tab:**
- View all entity states and attributes
- Check last_changed and last_updated timestamps
**Services Tab:**
- Test service calls manually
- Verify service parameters
**Events Tab:**
- Listen to all events
- See what events are being fired
### Enable Debug Logging
Add to `configuration.yaml`:
```yaml
logger:
default: info
logs:
homeassistant.core: debug
homeassistant.components.automation: debug
homeassistant.helpers.script: debug
```
Restart Home Assistant to enable debug logging.
### Check Error Log
Access via:
- UI: Settings > System > Logs
- API: `GET /api/error_log`
- File: `/config/home-assistant.log`
## Common Gotchas
1. **Entity ID changed:** Integrations sometimes change entity IDs on update
2. **State is string:** All states are strings, use `| int` or `| float` for math
3. **Time zone:** Ensure Home Assistant time zone matches your location
4. **Restart required:** Some configuration changes need full restart, not just reload
5. **YAML anchors:** Not supported in automations.yaml when using UI editor
6. **Case sensitivity:** Entity IDs and states are case-sensitive
7. **Reserved words:** Avoid using YAML reserved words as keys without quotes
8. **Whitespace:** Trailing whitespace can cause parsing issues

View File

@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""
Home Assistant REST API Client
This script provides utilities to interact with Home Assistant via the REST API.
Supports getting states, calling services, and querying configuration.
Usage:
# Get all entity states
python3 api_client.py --url http://homeassistant.local:8123 --token YOUR_TOKEN get-states
# Get specific entity state
python3 api_client.py --url http://homeassistant.local:8123 --token YOUR_TOKEN get-state light.living_room
# Call a service
python3 api_client.py --url http://homeassistant.local:8123 --token YOUR_TOKEN call-service light turn_on --entity light.living_room --data '{"brightness": 255}'
# List all services
python3 api_client.py --url http://homeassistant.local:8123 --token YOUR_TOKEN list-services
Environment variables:
HA_URL: Home Assistant URL (e.g., http://homeassistant.local:8123)
HA_TOKEN: Long-lived access token
"""
import argparse
import json
import os
import sys
from typing import Any, Dict, Optional
import urllib.request
import urllib.error
import urllib.parse
class HomeAssistantClient:
"""Client for interacting with Home Assistant REST API."""
def __init__(self, url: str, token: str):
"""Initialize the client with URL and access token."""
self.url = url.rstrip('/')
self.token = token
self.headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
def _request(self, endpoint: str, method: str = 'GET', data: Optional[Dict] = None) -> Any:
"""Make a request to the Home Assistant API."""
url = f"{self.url}/api/{endpoint}"
req = urllib.request.Request(url, headers=self.headers, method=method)
if data is not None:
req.data = json.dumps(data).encode('utf-8')
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8')
print(f"HTTP Error {e.code}: {e.reason}", file=sys.stderr)
print(f"Response: {error_body}", file=sys.stderr)
sys.exit(1)
except urllib.error.URLError as e:
print(f"URL Error: {e.reason}", file=sys.stderr)
sys.exit(1)
def get_states(self) -> list:
"""Get all entity states."""
return self._request('states')
def get_state(self, entity_id: str) -> Dict:
"""Get state of a specific entity."""
return self._request(f'states/{entity_id}')
def get_services(self) -> Dict:
"""Get all available services."""
return self._request('services')
def get_config(self) -> Dict:
"""Get Home Assistant configuration."""
return self._request('config')
def call_service(self, domain: str, service: str, entity_id: Optional[str] = None,
service_data: Optional[Dict] = None) -> list:
"""Call a service."""
data = {}
if entity_id:
data['entity_id'] = entity_id
if service_data:
data.update(service_data)
return self._request(f'services/{domain}/{service}', method='POST', data=data if data else None)
def get_error_log(self) -> str:
"""Get the error log."""
url = f"{self.url}/api/error_log"
req = urllib.request.Request(url, headers=self.headers)
with urllib.request.urlopen(req) as response:
return response.read().decode('utf-8')
def main():
parser = argparse.ArgumentParser(description='Home Assistant REST API Client')
parser.add_argument('--url', help='Home Assistant URL',
default=os.getenv('HA_URL'))
parser.add_argument('--token', help='Long-lived access token',
default=os.getenv('HA_TOKEN'))
subparsers = parser.add_subparsers(dest='command', help='Command to execute')
# get-states command
subparsers.add_parser('get-states', help='Get all entity states')
# get-state command
state_parser = subparsers.add_parser('get-state', help='Get specific entity state')
state_parser.add_argument('entity_id', help='Entity ID')
# list-services command
subparsers.add_parser('list-services', help='List all available services')
# get-config command
subparsers.add_parser('get-config', help='Get Home Assistant configuration')
# call-service command
service_parser = subparsers.add_parser('call-service', help='Call a service')
service_parser.add_argument('domain', help='Service domain (e.g., light, switch)')
service_parser.add_argument('service', help='Service name (e.g., turn_on, turn_off)')
service_parser.add_argument('--entity', help='Entity ID to target')
service_parser.add_argument('--data', help='Service data as JSON string')
# get-error-log command
subparsers.add_parser('get-error-log', help='Get the error log')
args = parser.parse_args()
if not args.url:
print("Error: Home Assistant URL is required (use --url or set HA_URL environment variable)", file=sys.stderr)
sys.exit(1)
if not args.token:
print("Error: Access token is required (use --token or set HA_TOKEN environment variable)", file=sys.stderr)
sys.exit(1)
if not args.command:
parser.print_help()
sys.exit(1)
client = HomeAssistantClient(args.url, args.token)
if args.command == 'get-states':
result = client.get_states()
elif args.command == 'get-state':
result = client.get_state(args.entity_id)
elif args.command == 'list-services':
result = client.get_services()
elif args.command == 'get-config':
result = client.get_config()
elif args.command == 'call-service':
service_data = json.loads(args.data) if args.data else None
result = client.call_service(args.domain, args.service, args.entity, service_data)
elif args.command == 'get-error-log':
result = client.get_error_log()
print(result)
return
print(json.dumps(result, indent=2))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""
List all Home Assistant entities and their states
This script queries Home Assistant and displays all entities with their current states.
Can filter by domain (e.g., light, switch, sensor) and format output.
Usage:
# List all entities
python3 list_entities.py --url http://homeassistant.local:8123 --token YOUR_TOKEN
# List only lights
python3 list_entities.py --url http://homeassistant.local:8123 --token YOUR_TOKEN --domain light
# Output as JSON
python3 list_entities.py --url http://homeassistant.local:8123 --token YOUR_TOKEN --format json
Environment variables:
HA_URL: Home Assistant URL
HA_TOKEN: Long-lived access token
"""
import argparse
import json
import os
import sys
import urllib.request
def get_entities(url: str, token: str):
"""Get all entity states from Home Assistant."""
api_url = f"{url.rstrip('/')}/api/states"
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
req = urllib.request.Request(api_url, headers=headers)
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description='List Home Assistant entities')
parser.add_argument('--url', help='Home Assistant URL',
default=os.getenv('HA_URL'))
parser.add_argument('--token', help='Long-lived access token',
default=os.getenv('HA_TOKEN'))
parser.add_argument('--domain', help='Filter by domain (e.g., light, switch, sensor)')
parser.add_argument('--format', choices=['table', 'json', 'list'],
default='table', help='Output format')
args = parser.parse_args()
if not args.url or not args.token:
print("Error: URL and token are required", file=sys.stderr)
sys.exit(1)
entities = get_entities(args.url, args.token)
# Filter by domain if specified
if args.domain:
entities = [e for e in entities if e['entity_id'].startswith(f"{args.domain}.")]
if args.format == 'json':
print(json.dumps(entities, indent=2))
elif args.format == 'list':
for entity in entities:
print(entity['entity_id'])
else: # table format
print(f"{'Entity ID':<40} {'State':<20} {'Last Changed':<20}")
print("-" * 80)
for entity in entities:
entity_id = entity['entity_id']
state = entity['state']
last_changed = entity.get('last_changed', 'N/A')[:19] # Truncate timestamp
print(f"{entity_id:<40} {state:<20} {last_changed:<20}")
print(f"\nTotal: {len(entities)} entities")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""
List all available Home Assistant services
This script queries Home Assistant and displays all available services organized by domain.
Usage:
# List all services
python3 list_services.py --url http://homeassistant.local:8123 --token YOUR_TOKEN
# List services for specific domain
python3 list_services.py --url http://homeassistant.local:8123 --token YOUR_TOKEN --domain light
# Output as JSON
python3 list_services.py --url http://homeassistant.local:8123 --token YOUR_TOKEN --format json
Environment variables:
HA_URL: Home Assistant URL
HA_TOKEN: Long-lived access token
"""
import argparse
import json
import os
import sys
import urllib.request
def get_services(url: str, token: str):
"""Get all services from Home Assistant."""
api_url = f"{url.rstrip('/')}/api/services"
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
req = urllib.request.Request(api_url, headers=headers)
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description='List Home Assistant services')
parser.add_argument('--url', help='Home Assistant URL',
default=os.getenv('HA_URL'))
parser.add_argument('--token', help='Long-lived access token',
default=os.getenv('HA_TOKEN'))
parser.add_argument('--domain', help='Filter by domain (e.g., light, switch)')
parser.add_argument('--format', choices=['tree', 'json', 'list'],
default='tree', help='Output format')
args = parser.parse_args()
if not args.url or not args.token:
print("Error: URL and token are required", file=sys.stderr)
sys.exit(1)
services = get_services(args.url, args.token)
# Filter by domain if specified
if args.domain:
services = {args.domain: services.get(args.domain, {})}
if args.format == 'json':
print(json.dumps(services, indent=2))
elif args.format == 'list':
for domain, domain_services in sorted(services.items()):
for service in sorted(domain_services.keys()):
print(f"{domain}.{service}")
else: # tree format
for domain, domain_services in sorted(services.items()):
print(f"\n{domain}:")
for service, details in sorted(domain_services.items()):
description = details.get('description', 'No description')
print(f"{service}")
if description and description != 'No description':
print(f" {description}")
# Show fields if available
if 'fields' in details and details['fields']:
print(f" Fields:")
for field_name, field_info in details['fields'].items():
field_desc = field_info.get('description', '')
print(f" - {field_name}: {field_desc}")
print(f"\nTotal: {sum(len(s) for s in services.values())} services across {len(services)} domains")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""
Home Assistant YAML Configuration Validator
This script validates Home Assistant YAML configuration files for syntax errors
and common issues.
Usage:
# Validate a single file
python3 validate_yaml.py configuration.yaml
# Validate automation configuration
python3 validate_yaml.py automations.yaml --type automation
# Validate with verbose output
python3 validate_yaml.py configuration.yaml --verbose
"""
import argparse
import sys
import yaml
from pathlib import Path
from typing import Any, Dict, List, Optional
class HomeAssistantYAMLValidator:
"""Validator for Home Assistant YAML files."""
# Common required fields for different config types
AUTOMATION_REQUIRED = ['trigger']
AUTOMATION_OPTIONAL = ['alias', 'id', 'description', 'condition', 'action', 'mode', 'variables']
def __init__(self, verbose: bool = False):
self.verbose = verbose
self.errors: List[str] = []
self.warnings: List[str] = []
def validate_file(self, filepath: Path) -> bool:
"""Validate a YAML file."""
self.errors = []
self.warnings = []
if not filepath.exists():
self.errors.append(f"File not found: {filepath}")
return False
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Check for tabs (Home Assistant doesn't allow tabs)
if '\t' in content:
self.errors.append("File contains tab characters. Use spaces for indentation.")
return False
# Parse YAML
data = yaml.safe_load(content)
if self.verbose:
print(f"✓ YAML syntax is valid")
return True
except yaml.YAMLError as e:
self.errors.append(f"YAML syntax error: {str(e)}")
return False
except Exception as e:
self.errors.append(f"Error reading file: {str(e)}")
return False
def validate_automation(self, filepath: Path) -> bool:
"""Validate an automation configuration file."""
if not self.validate_file(filepath):
return False
try:
with open(filepath, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
if not isinstance(data, list):
# Single automation wrapped in automation: key
if isinstance(data, dict):
if 'automation' in data:
automations = data['automation']
if not isinstance(automations, list):
automations = [automations]
else:
automations = [data]
else:
self.errors.append("Automation file must contain a list or dict")
return False
else:
automations = data
# Validate each automation
for i, automation in enumerate(automations):
if not isinstance(automation, dict):
self.errors.append(f"Automation {i+1} is not a dictionary")
continue
# Check required fields
if 'trigger' not in automation:
self.errors.append(f"Automation {i+1}: Missing required field 'trigger'")
# Check for common issues
if 'alias' not in automation:
self.warnings.append(f"Automation {i+1}: No 'alias' field (recommended for readability)")
if 'id' not in automation:
self.warnings.append(f"Automation {i+1}: No 'id' field (required for UI editor)")
# Validate trigger structure
if 'trigger' in automation:
triggers = automation['trigger']
if not isinstance(triggers, list):
triggers = [triggers]
for j, trigger in enumerate(triggers):
if not isinstance(trigger, dict):
self.errors.append(f"Automation {i+1}, Trigger {j+1}: Must be a dictionary")
continue
if 'trigger' not in trigger and 'platform' not in trigger:
self.errors.append(f"Automation {i+1}, Trigger {j+1}: Missing 'trigger' or 'platform' field")
# Validate action structure if present
if 'action' in automation:
actions = automation['action']
if not isinstance(actions, list):
actions = [actions]
for j, action in enumerate(actions):
if not isinstance(action, dict):
self.errors.append(f"Automation {i+1}, Action {j+1}: Must be a dictionary")
if self.verbose and not self.errors:
print(f"✓ Validated {len(automations)} automation(s)")
return len(self.errors) == 0
except Exception as e:
self.errors.append(f"Error validating automation: {str(e)}")
return False
def print_results(self):
"""Print validation results."""
if self.errors:
print("\n❌ Errors found:")
for error in self.errors:
print(f"{error}")
if self.warnings:
print("\n⚠️ Warnings:")
for warning in self.warnings:
print(f"{warning}")
if not self.errors and not self.warnings:
print("✅ Validation passed with no errors or warnings")
elif not self.errors:
print("\n✅ Validation passed (with warnings)")
def main():
parser = argparse.ArgumentParser(description='Validate Home Assistant YAML files')
parser.add_argument('file', help='YAML file to validate')
parser.add_argument('--type', choices=['automation', 'general'],
default='general', help='Type of configuration to validate')
parser.add_argument('--verbose', '-v', action='store_true',
help='Verbose output')
args = parser.parse_args()
filepath = Path(args.file)
validator = HomeAssistantYAMLValidator(verbose=args.verbose)
if args.type == 'automation':
success = validator.validate_automation(filepath)
else:
success = validator.validate_file(filepath)
validator.print_results()
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()