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:
265
skills/homeassistant/SKILL.md
Normal file
265
skills/homeassistant/SKILL.md
Normal 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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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."
|
||||
@@ -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"
|
||||
284
skills/homeassistant/references/api_reference.md
Normal file
284
skills/homeassistant/references/api_reference.md
Normal 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.
|
||||
389
skills/homeassistant/references/automation_reference.md
Normal file
389
skills/homeassistant/references/automation_reference.md
Normal 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
|
||||
379
skills/homeassistant/references/common_patterns.md
Normal file
379
skills/homeassistant/references/common_patterns.md
Normal 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
|
||||
```
|
||||
342
skills/homeassistant/references/troubleshooting.md
Normal file
342
skills/homeassistant/references/troubleshooting.md
Normal 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
|
||||
172
skills/homeassistant/scripts/api_client.py
Executable file
172
skills/homeassistant/scripts/api_client.py
Executable 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()
|
||||
88
skills/homeassistant/scripts/list_entities.py
Executable file
88
skills/homeassistant/scripts/list_entities.py
Executable 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()
|
||||
95
skills/homeassistant/scripts/list_services.py
Executable file
95
skills/homeassistant/scripts/list_services.py
Executable 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()
|
||||
186
skills/homeassistant/scripts/validate_yaml.py
Executable file
186
skills/homeassistant/scripts/validate_yaml.py
Executable 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()
|
||||
Reference in New Issue
Block a user