Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:59:39 +08:00
commit 0b993003eb
9 changed files with 2987 additions and 0 deletions

View File

@@ -0,0 +1,282 @@
---
name: home-assistant
description: Use this if the user wants to connect to Home Assistant or leverage Home Assistant in any shape or form inside their project. Guide users integrating Home Assistant into projects for home automation control or data ingestion. Collects and validates connection credentials (URL and Long-Lived Access Token), provides API reference documentation for Python and Node.js implementations, and helps integrate Home Assistant APIs into user projects.
---
# Home Assistant
## Overview
This skill helps users integrate Home Assistant into their projects, whether to control smart home devices or to ingest sensor data and state information. The skill guides users through connection setup, validates credentials, and provides comprehensive API reference documentation for both Python and Node.js.
## When to Use This Skill
Use this skill when users want to:
- Connect their application to Home Assistant
- Control smart home devices (lights, switches, thermostats, etc.)
- Read sensor data or entity states from Home Assistant
- Automate home control based on custom logic
- Build dashboards or monitoring tools using Home Assistant data
- Integrate Home Assistant into existing Python or Node.js projects
## Connection Setup Workflow
### Step 1: Collect Connection Information
Collect two pieces of information from the user:
1. **Home Assistant URL**: The web address where Home Assistant is accessible
2. **Long-Lived Access Token**: Authentication token for API access
### Step 2: Normalize the URL
If the user provides a URL with a path component (e.g., `http://homeassistant.local:8123/lovelace/dashboard`), normalize it by removing everything after the host and port. The base URL should only include the scheme, host, and port:
- ✓ Correct: `http://homeassistant.local:8123`
- ✗ Incorrect: `http://homeassistant.local:8123/lovelace/dashboard`
### Step 3: Help Users Find Their Token
If users don't know where to find their Long-Lived Access Token, provide these instructions:
1. Log into Home Assistant web interface
2. Click on the user profile (bottom left, user icon or name)
3. Click on the "Security" tab
4. Scroll down to the "Long-Lived Access Tokens" section
5. Click "Create Token"
6. Give the token a name (e.g., "My Project")
7. Copy the generated token (it will only be shown once)
### Step 4: Validate the Connection
Use curl to test the connection and retrieve Home Assistant configuration information.
```bash
curl -X GET \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
<URL>/api/config
```
Example:
```bash
curl -X GET \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
http://homeassistant.local:8123/api/config
```
**Success output:**
```json
{
"location_name": "Home",
"latitude": 37.7749,
"longitude": -122.4194,
"elevation": 0,
"unit_system": {
"length": "km",
"mass": "g",
"temperature": "°C",
"volume": "L"
},
"time_zone": "America/Los_Angeles",
"version": "2024.1.0",
"config_dir": "/config",
"allowlist_external_dirs": [],
"allowlist_external_urls": [],
"components": ["automation", "light", "switch", ...],
"config_source": "storage"
}
```
**Key information from the response:**
- `version`: Home Assistant version (e.g., "2024.1.0")
- `location_name`: Name of the Home Assistant instance
- `time_zone`: Configured time zone
- `components`: List of loaded components/integrations
**Failure scenarios:**
Authentication failure (401):
```json
{"message": "Invalid authentication"}
```
Connection failure:
```
curl: (7) Failed to connect to homeassistant.local port 8123: Connection refused
```
If authentication fails, verify:
1. The Long-Lived Access Token is correct
2. The token hasn't been deleted or expired
3. The URL is correct (including http/https and port)
### Step 5: Proceed with Implementation
Once the connection is validated, help the user implement their integration based on their programming language and requirements.
## Core Interaction Patterns
**IMPORTANT**: The following WebSocket API commands form the **core** of how users should interact with Home Assistant. These leverage the automation engine and keep scripts minimal by using native Home Assistant syntax.
### Automation Engine Commands (WebSocket API)
These commands require WebSocket API connection and provide the most powerful and flexible way to interact with Home Assistant:
#### 1. subscribe_trigger - Listen for Specific Events
**Use this when**: You want to be notified when specific conditions occur (state changes, time patterns, webhooks, etc.)
**Command structure**:
```json
{
"type": "subscribe_trigger",
"trigger": {
"platform": "state",
"entity_id": "binary_sensor.motion_sensor",
"to": "on"
},
"variables": {
"custom_var": "value"
}
}
```
**Why use this**: Instead of subscribing to all state changes and filtering, subscribe directly to the triggers you care about. This is more efficient and uses Home Assistant's native trigger syntax.
#### 2. test_condition - Test Conditions Server-Side
**Use this when**: You need to check if a condition is met without implementing the logic in your script
**Command structure**:
```json
{
"type": "test_condition",
"condition": {
"condition": "numeric_state",
"entity_id": "sensor.temperature",
"above": 20
},
"variables": {
"custom_var": "value"
}
}
```
**Why use this**: Offload condition logic to Home Assistant. Your script stays simple while using Home Assistant's powerful condition engine.
#### 3. execute_script - Execute Multiple Actions
**Use this when**: You need to execute a sequence of actions, including `wait_for_trigger`, delays, service calls, and more
**Command structure**:
```json
{
"type": "execute_script",
"sequence": [
{
"service": "light.turn_on",
"target": {"entity_id": "light.living_room"}
},
{
"wait_for_trigger": [
{
"platform": "state",
"entity_id": "binary_sensor.motion",
"to": "off",
"for": {"minutes": 5}
}
]
},
{
"service": "light.turn_off",
"target": {"entity_id": "light.living_room"}
}
],
"variables": {
"custom_var": "value"
}
}
```
**Why use this**:
- Execute complex automation logic using native Home Assistant syntax
- Use `wait_for_trigger` to wait for events
- Chain multiple actions together
- Keep your script minimal - all logic is in HA syntax
- **Getting response data**: To get response from service calls, store the result in a response variable and set it as the script result
**Example with response data**:
```json
{
"type": "execute_script",
"sequence": [
{
"service": "weather.get_forecasts",
"target": {"entity_id": "weather.home"},
"response_variable": "weather_data"
},
{
"stop": "Done",
"response_variable": "weather_data"
}
]
}
```
### Essential Registry Information
To understand Home Assistant's information architecture, also use:
- **config/entity_registry/list**: Learn about entities and their unique IDs
- **config/device_registry/list**: Learn about devices and their entities
- **config/area_registry/list**: Understand how spaces are organized
- **config/floor_registry/list**: Multi-floor layout information
### Current state of the home
If the user is building an application that wants to represent the current state of the home, use:
- **subscribe_entities**: Get real-time updates on all entity states (Home Assistant JS WebSocket has built-in support for this)
## Implementation Guidance
### Python Projects
For Python-based projects, refer to the Python API reference:
- **File**: `references/python_api.md`
- **Usage**: Load this reference when implementing Python integrations
- **Contains**:
- **Example code**: Python scripts demonstrating common use cases.
- **Key operations**: Automation engine commands, getting states, calling services, subscribing to events, error handling
### Node.js Projects
For Node.js-based projects, refer to the Node.js API reference:
- **File**: `references/node_api.md`
- **Usage**: Load this reference when implementing Node.js integrations
- **Contains**:
- WebSocket API examples using `home-assistant-js-websocket` library
## Best Practices
1. **Error Handling**: Always implement proper error handling for network failures and authentication issues
2. **Connection Testing**: Validate connections before proceeding with implementation
3. **Real-time Updates**: For monitoring scenarios, use WebSocket APIs instead of polling REST endpoints
## Common Integration Patterns
### Data Dashboard
Read sensor states and display them in a custom dashboard or monitoring application.
### Automation Logic
Subscribe to entity state changes and trigger custom actions based on conditions.
### External Triggers
Call Home Assistant services from external events (webhooks, scheduled jobs, user actions).
### Data Export
Retrieve historical data from Home Assistant for analysis or backup purposes.

View File

@@ -0,0 +1,565 @@
# Home Assistant Node.js API Reference
This document provides guidance on using the Home Assistant WebSocket API with Node.js using the `home-assistant-js-websocket` library.
**RECOMMENDED APPROACH**: For monitoring entity states, always prefer using `subscribeEntities` from `home-assistant-js-websocket` instead of manually subscribing to `state_changed` events. See [Subscribe to All Entity State Changes](#subscribe-to-all-entity-state-changes).
## Installation
For Node.js 22+, you only need to install the library (built-in WebSocket support):
```bash
npm install home-assistant-js-websocket
```
For older Node.js versions (< 22), also install the `ws` package:
```bash
npm install home-assistant-js-websocket ws
```
## Authentication with Long-Lived Access Token
```javascript
import {
createConnection,
createLongLivedTokenAuth,
subscribeEntities,
callService,
} from "home-assistant-js-websocket";
const auth = createLongLivedTokenAuth(
"http://homeassistant.local:8123",
"YOUR_LONG_LIVED_ACCESS_TOKEN"
);
const connection = await createConnection({ auth });
console.log("Connected to Home Assistant!");
```
## Connection Validation
### Get Home Assistant Configuration and Version
After connecting, you can retrieve configuration information including the Home Assistant version:
```javascript
import { getConfig } from "home-assistant-js-websocket";
const config = await getConfig(connection);
console.log(`Home Assistant Version: ${config.version}`);
console.log(`Location: ${config.location_name}`);
console.log(`Time Zone: ${config.time_zone}`);
console.log(`Components loaded: ${config.components.length}`);
```
The config object contains:
- `version`: Home Assistant version (e.g., "2024.1.0")
- `location_name`: Name of the instance
- `time_zone`: Configured time zone
- `unit_system`: Units for measurements (length, mass, temperature, volume)
- `components`: Array of all loaded integrations
- `latitude`, `longitude`, `elevation`: Location data
## Getting Entity States
### Subscribe to All Entity State Changes
**PREFERRED METHOD**: Use `subscribeEntities` for real-time entity state monitoring.
**Function Signature:**
```typescript
export const subscribeEntities = (
conn: Connection,
onChange: (state: HassEntities) => void,
): UnsubscribeFunc => entitiesColl(conn).subscribe(onChange);
```
**Why use subscribeEntities:**
- Automatically maintains a complete, up-to-date map of all entities
- More efficient than manually tracking state_changed events
- Handles entity additions, deletions, and updates automatically
- Provides clean HassEntities object indexed by entity_id
**Example:**
```javascript
import { subscribeEntities } from "home-assistant-js-websocket";
const unsubscribe = subscribeEntities(connection, (entities) => {
// Called whenever any entity state changes
// 'entities' is a complete map of all entity states
console.log("Entities updated:", entities);
// Access specific entity by ID
const light = entities["light.living_room"];
if (light) {
console.log(`Light state: ${light.state}`);
console.log(`Brightness: ${light.attributes.brightness}`);
}
// Monitor multiple entities
const temp = entities["sensor.temperature"];
const humidity = entities["sensor.humidity"];
if (temp && humidity) {
console.log(`Temp: ${temp.state}°C, Humidity: ${humidity.state}%`);
}
});
// To stop receiving updates
// unsubscribe();
```
### Get Current States Once
```javascript
import { getStates } from "home-assistant-js-websocket";
const states = await getStates(connection);
for (const state of states) {
console.log(`${state.entity_id}: ${state.state}`);
}
```
### Get Specific Entity State
```javascript
import { getStates } from "home-assistant-js-websocket";
const states = await getStates(connection);
const light = states.find(s => s.entity_id === "light.living_room");
if (light) {
console.log(`State: ${light.state}`);
console.log(`Attributes:`, light.attributes);
}
```
## Calling Services
### Turn on a Light
```javascript
await callService(connection, "light", "turn_on", {
entity_id: "light.living_room",
brightness: 255,
rgb_color: [255, 0, 0], // Red
});
```
### Turn off a Switch
```javascript
await callService(connection, "switch", "turn_off", {
entity_id: "switch.bedroom_fan",
});
```
### Set Thermostat Temperature
```javascript
await callService(connection, "climate", "set_temperature", {
entity_id: "climate.living_room",
temperature: 22,
});
```
### Send Notification
```javascript
await callService(connection, "notify", "notify", {
message: "Hello from Node.js!",
title: "Notification Title",
});
```
### Common Service Patterns
```javascript
// Light control
await callService(connection, "light", "turn_on", {
entity_id: "light.bedroom",
brightness_pct: 50,
});
// Switch control
await callService(connection, "switch", "toggle", {
entity_id: "switch.living_room_lamp",
});
// Cover control
await callService(connection, "cover", "open_cover", {
entity_id: "cover.garage_door",
});
// Media player control
await callService(connection, "media_player", "play_media", {
entity_id: "media_player.living_room",
media_content_id: "https://example.com/song.mp3",
media_content_type: "music",
});
```
## Automation Engine Commands (RECOMMENDED)
**IMPORTANT**: These commands form the **core** of how you should interact with Home Assistant. They leverage the automation engine and keep your code minimal by using native Home Assistant syntax.
### subscribe_trigger - Listen for Specific Events
**PREFERRED METHOD** for listening to specific state changes, time patterns, webhooks, etc.
```javascript
// Subscribe to a state trigger
const unsubscribe = await connection.subscribeMessage(
(message) => {
console.log("Trigger fired!", message);
console.log("Variables:", message.variables);
},
{
type: "subscribe_trigger",
trigger: {
platform: "state",
entity_id: "binary_sensor.motion_sensor",
to: "on"
},
variables: {
custom_var: "value"
}
}
);
// Unsubscribe when done
// unsubscribe();
```
**More trigger examples**:
```javascript
// Time pattern trigger
await connection.subscribeMessage(
(message) => console.log("Every 5 minutes!", message),
{
type: "subscribe_trigger",
trigger: {
platform: "time_pattern",
minutes: "/5"
}
}
);
// Numeric state trigger
await connection.subscribeMessage(
(message) => console.log("Temperature above 25°C!", message),
{
type: "subscribe_trigger",
trigger: {
platform: "numeric_state",
entity_id: "sensor.temperature",
above: 25
}
}
);
// Template trigger
await connection.subscribeMessage(
(message) => console.log("Sun is up!", message),
{
type: "subscribe_trigger",
trigger: {
platform: "template",
value_template: "{{ states('sun.sun') == 'above_horizon' }}"
}
}
);
```
### test_condition - Test Conditions Server-Side
Test conditions without implementing logic in your code:
```javascript
// Test a numeric state condition
const result = await connection.sendMessagePromise({
type: "test_condition",
condition: {
condition: "numeric_state",
entity_id: "sensor.temperature",
above: 20
}
});
if (result.result) {
console.log("Temperature is above 20°C");
}
```
**More condition examples**:
```javascript
// State condition
const result = await connection.sendMessagePromise({
type: "test_condition",
condition: {
condition: "state",
entity_id: "light.living_room",
state: "on"
}
});
// Time condition
const result = await connection.sendMessagePromise({
type: "test_condition",
condition: {
condition: "time",
after: "18:00:00",
before: "23:00:00"
}
});
// Template condition
const result = await connection.sendMessagePromise({
type: "test_condition",
condition: {
condition: "template",
value_template: "{{ is_state('sun.sun', 'above_horizon') }}"
}
});
// And/Or conditions
const result = await connection.sendMessagePromise({
type: "test_condition",
condition: {
condition: "and",
conditions: [
{
condition: "state",
entity_id: "binary_sensor.motion",
state: "on"
},
{
condition: "numeric_state",
entity_id: "sensor.light_level",
below: 100
}
]
}
});
```
### execute_script - Execute Multiple Actions
**MOST POWERFUL METHOD**: Execute sequences of actions using Home Assistant's native syntax.
```javascript
// Simple sequence
const result = await connection.sendMessagePromise({
type: "execute_script",
sequence: [
{
service: "light.turn_on",
target: { entity_id: "light.living_room" },
data: { brightness: 255 }
},
{
delay: { seconds: 5 }
},
{
service: "light.turn_off",
target: { entity_id: "light.living_room" }
}
]
});
```
**Advanced: Using wait_for_trigger**
```javascript
// Turn on light and wait for motion to stop
const result = await connection.sendMessagePromise({
type: "execute_script",
sequence: [
{
service: "light.turn_on",
target: { entity_id: "light.living_room" }
},
{
wait_for_trigger: [
{
platform: "state",
entity_id: "binary_sensor.motion",
to: "off",
for: { minutes: 5 }
}
],
timeout: { hours: 2 }
},
{
service: "light.turn_off",
target: { entity_id: "light.living_room" }
}
]
});
```
**Getting response data from service calls**:
```javascript
// Call a service and get the response
const result = await connection.sendMessagePromise({
type: "execute_script",
sequence: [
{
service: "weather.get_forecasts",
target: { entity_id: "weather.home" },
data: { type: "daily" },
response_variable: "weather_data"
},
{
stop: "Done",
response_variable: "weather_data"
}
]
});
console.log("Weather forecast:", result.response_variable);
```
**Complex automation example**:
```javascript
// Full automation logic in execute_script
const result = await connection.sendMessagePromise({
type: "execute_script",
sequence: [
// Check if it's dark
{
condition: "numeric_state",
entity_id: "sensor.light_level",
below: 100
},
// Turn on lights
{
service: "light.turn_on",
target: { area_id: "living_room" },
data: { brightness_pct: 50 }
},
// Wait for motion to stop for 10 minutes
{
wait_for_trigger: [
{
platform: "state",
entity_id: "binary_sensor.motion",
to: "off",
for: { minutes: 10 }
}
],
timeout: { hours: 4 }
},
// Turn off lights
{
service: "light.turn_off",
target: { area_id: "living_room" }
}
]
});
```
## Error Handling
```javascript
import {
createConnection,
createLongLivedTokenAuth,
ERR_INVALID_AUTH,
ERR_CONNECTION_LOST,
} from "home-assistant-js-websocket";
try {
const auth = createLongLivedTokenAuth(url, token);
const connection = await createConnection({ auth });
console.log("Connected successfully!");
} catch (err) {
if (err === ERR_INVALID_AUTH) {
console.error("Invalid authentication - check your token");
} else if (err === ERR_CONNECTION_LOST) {
console.error("Connection lost - check your URL and network");
} else {
console.error("Connection failed:", err);
}
}
```
## Complete Example
```javascript
import {
createConnection,
createLongLivedTokenAuth,
getConfig,
subscribeEntities,
callService,
} from "home-assistant-js-websocket";
async function main() {
// Connect
const auth = createLongLivedTokenAuth(
"http://homeassistant.local:8123",
"YOUR_TOKEN"
);
const connection = await createConnection({ auth });
console.log("✓ Connected to Home Assistant");
// Get configuration and version
const config = await getConfig(connection);
console.log(`✓ Home Assistant ${config.version}`);
console.log(` Location: ${config.location_name}`);
// Subscribe to entity changes
subscribeEntities(connection, (entities) => {
const temp = entities["sensor.living_room_temperature"];
if (temp) {
console.log(`Temperature: ${temp.state}°C`);
// Auto-control based on temperature
if (parseFloat(temp.state) > 25) {
callService(connection, "switch", "turn_on", {
entity_id: "switch.fan",
});
}
}
});
// Call a service
await callService(connection, "light", "turn_on", {
entity_id: "light.living_room",
});
console.log("✓ Light turned on");
}
main().catch(console.error);
```
## Using with Node.js < 22
For older Node.js versions, configure the WebSocket implementation:
```javascript
import ws from "ws";
const connection = await createConnection({
auth,
createSocket: (auth) => {
return new ws(auth.wsUrl, {
rejectUnauthorized: false, // Only for self-signed certs
});
},
});
```
## Official Documentation
For complete library documentation, see:
- https://github.com/home-assistant/home-assistant-js-websocket
- https://developers.home-assistant.io/docs/api/websocket/

View File

@@ -0,0 +1,649 @@
# Home Assistant Python API Reference
This document provides guidance on using the Home Assistant WebSocket and REST APIs with Python.
**RECOMMENDED**: Use the WebSocket API with automation engine commands for all interactions with Home Assistant. This is the most powerful and efficient approach.
## Table of Contents
1. [Authentication](#authentication)
2. [WebSocket API (RECOMMENDED)](#websocket-api-recommended)
- [Connection Setup](#connection-setup)
- [subscribe_trigger - Listen for Events](#subscribe_trigger---listen-for-events)
- [test_condition - Test Conditions](#test_condition---test-conditions)
- [execute_script - Execute Actions](#execute_script---execute-actions)
- [subscribe_entities - Monitor All Entities](#subscribe_entities---monitor-all-entities)
3. [REST API (Optional)](#rest-api-optional)
- [Connection Validation](#connection-validation)
- [Basic Queries](#basic-queries)
4. [PEP 723 Inline Script Metadata](#pep-723-inline-script-metadata)
5. [Official Documentation](#official-documentation)
## Authentication
All API requests require a Long-Lived Access Token. For WebSocket connections, you'll authenticate after connecting. For REST API requests, include the token in the Authorization header.
**WebSocket Authentication**: Handled automatically by the connection setup (see below)
**REST API Authentication**:
```python
import httpx
url = "http://homeassistant.local:8123"
token = "YOUR_LONG_LIVED_ACCESS_TOKEN"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
```
## WebSocket API (RECOMMENDED)
**This is the primary way to interact with Home Assistant.** The WebSocket API provides access to the automation engine, allowing you to use native Home Assistant syntax for triggers, conditions, and actions.
### Connection Setup
First, establish a WebSocket connection:
```python
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "websocket-client>=1.6.0",
# ]
# ///
import websocket
import json
import threading
import time
class HomeAssistantWebSocket:
def __init__(self, url, token):
self.url = url.replace("http://", "ws://").replace("https://", "wss://")
self.token = token
self.ws = None
self.msg_id = 1
self.callbacks = {}
self.authenticated = False
def connect(self):
"""Connect to Home Assistant WebSocket API."""
self.ws = websocket.WebSocketApp(
f"{self.url}/api/websocket",
on_message=self._on_message,
on_open=self._on_open,
on_error=self._on_error
)
# Run WebSocket in background thread
wst = threading.Thread(target=self.ws.run_forever)
wst.daemon = True
wst.start()
# Wait for authentication
timeout = 5
start = time.time()
while not self.authenticated and time.time() - start < timeout:
time.sleep(0.1)
def _on_open(self, ws):
"""Handle WebSocket connection open."""
print("Connected to Home Assistant")
def _on_error(self, ws, error):
"""Handle WebSocket errors."""
print(f"WebSocket error: {error}")
def _on_message(self, ws, message):
"""Handle incoming messages."""
data = json.loads(message)
if data.get("type") == "auth_required":
# Send authentication
ws.send(json.dumps({
"type": "auth",
"access_token": self.token
}))
elif data.get("type") == "auth_ok":
print("Authenticated successfully")
self.authenticated = True
elif data.get("type") == "auth_invalid":
print("Authentication failed - check your token")
elif data.get("id") in self.callbacks:
# Call the registered callback
self.callbacks[data["id"]](data)
def send_command(self, command, callback=None):
"""Send a command and optionally register a callback."""
msg_id = self.msg_id
self.msg_id += 1
command["id"] = msg_id
if callback:
self.callbacks[msg_id] = callback
self.ws.send(json.dumps(command))
return msg_id
# Usage
ha = HomeAssistantWebSocket("http://homeassistant.local:8123", "YOUR_TOKEN")
ha.connect()
```
### subscribe_trigger - Listen for Events
**PREFERRED METHOD** for listening to specific state changes, time patterns, numeric thresholds, and more.
**Why use this**: Instead of filtering all state changes yourself, let Home Assistant's automation engine notify you only when your specific conditions are met.
```python
# Subscribe to motion sensor state change
def on_motion_detected(message):
print(f"Motion detected! {message}")
# Your logic here
ha.send_command({
"type": "subscribe_trigger",
"trigger": {
"platform": "state",
"entity_id": "binary_sensor.motion_sensor",
"to": "on"
}
}, on_motion_detected)
```
**More trigger examples**:
```python
# Time pattern - every 5 minutes
ha.send_command({
"type": "subscribe_trigger",
"trigger": {
"platform": "time_pattern",
"minutes": "/5"
}
}, lambda msg: print(f"5 minutes passed"))
# Numeric state - temperature above threshold
ha.send_command({
"type": "subscribe_trigger",
"trigger": {
"platform": "numeric_state",
"entity_id": "sensor.temperature",
"above": 25
}
}, lambda msg: print(f"Temperature above 25°C!"))
# State change with duration
ha.send_command({
"type": "subscribe_trigger",
"trigger": {
"platform": "state",
"entity_id": "binary_sensor.motion",
"to": "off",
"for": {"minutes": 5}
}
}, lambda msg: print(f"No motion for 5 minutes"))
# Template trigger
ha.send_command({
"type": "subscribe_trigger",
"trigger": {
"platform": "template",
"value_template": "{{ states('sun.sun') == 'above_horizon' }}"
}
}, lambda msg: print(f"Sun is up!"))
# Multiple triggers
ha.send_command({
"type": "subscribe_trigger",
"trigger": [
{
"platform": "state",
"entity_id": "binary_sensor.door",
"to": "on"
},
{
"platform": "state",
"entity_id": "binary_sensor.window",
"to": "on"
}
]
}, lambda msg: print(f"Door or window opened!"))
```
### execute_script - Execute Actions
**MOST POWERFUL METHOD**: Execute sequences of actions using Home Assistant's native syntax.
**Why use this**:
- Execute complex automation logic
- Use `wait_for_trigger` to wait for events
- Chain multiple actions together
- Keep your script minimal - all logic is in HA syntax
- Get response data from service calls
```python
def on_complete(message):
print(f"Script completed: {message}")
# Simple sequence
ha.send_command({
"type": "execute_script",
"sequence": [
{
"service": "light.turn_on",
"target": {"entity_id": "light.living_room"},
"data": {"brightness": 255}
},
{
"delay": {"seconds": 5}
},
{
"service": "light.turn_off",
"target": {"entity_id": "light.living_room"}
}
]
}, on_complete)
```
**Advanced: Using wait_for_trigger**
```python
# Turn on light when motion detected, turn off after 5 minutes of no motion
ha.send_command({
"type": "execute_script",
"sequence": [
{
"service": "light.turn_on",
"target": {"entity_id": "light.living_room"}
},
{
"wait_for_trigger": [
{
"platform": "state",
"entity_id": "binary_sensor.motion",
"to": "off",
"for": {"minutes": 5}
}
],
"timeout": {"hours": 2}
},
{
"service": "light.turn_off",
"target": {"entity_id": "light.living_room"}
}
]
}, on_complete)
```
**Getting response data from service calls**:
```python
def on_weather(message):
weather_data = message.get("result", {}).get("response_variable")
print(f"Weather forecast: {weather_data}")
ha.send_command({
"type": "execute_script",
"sequence": [
{
"service": "weather.get_forecasts",
"target": {"entity_id": "weather.home"},
"data": {"type": "daily"},
"response_variable": "weather_data"
},
{
"stop": "Done",
"response_variable": "weather_data"
}
]
}, on_weather)
```
**Complex automation example**:
```python
# Full automation logic: turn on lights when dark, turn off after no motion
ha.send_command({
"type": "execute_script",
"sequence": [
# Check if it's dark
{
"condition": "numeric_state",
"entity_id": "sensor.light_level",
"below": 100
},
# Turn on lights at 50% brightness
{
"service": "light.turn_on",
"target": {"area_id": "living_room"},
"data": {"brightness_pct": 50}
},
# Wait for motion to stop for 10 minutes
{
"wait_for_trigger": [
{
"platform": "state",
"entity_id": "binary_sensor.motion",
"to": "off",
"for": {"minutes": 10}
}
],
"timeout": {"hours": 4}
},
# Turn off lights
{
"service": "light.turn_off",
"target": {"area_id": "living_room"}
}
]
}, on_complete)
```
**Using conditions and choose**:
```python
# Different actions based on time of day
ha.send_command({
"type": "execute_script",
"sequence": [
{
"choose": [
{
"conditions": {
"condition": "time",
"after": "06:00:00",
"before": "22:00:00"
},
"sequence": [
{
"service": "light.turn_on",
"target": {"entity_id": "light.living_room"},
"data": {"brightness_pct": 100}
}
]
}
],
"default": [
{
"service": "light.turn_on",
"target": {"entity_id": "light.living_room"},
"data": {"brightness_pct": 20}
}
]
}
]
}, on_complete)
```
### test_condition - Test Conditions
Test conditions server-side without implementing logic in your code.
**Why use this**: Offload condition logic to Home Assistant. Your script stays simple while using HA's powerful condition engine.
```python
def check_result(message):
if message.get("result", {}).get("result"):
print("Condition is true")
else:
print("Condition is false")
# Numeric state condition
ha.send_command({
"type": "test_condition",
"condition": {
"condition": "numeric_state",
"entity_id": "sensor.temperature",
"above": 20
}
}, check_result)
```
**More condition examples**:
```python
# State condition
ha.send_command({
"type": "test_condition",
"condition": {
"condition": "state",
"entity_id": "light.living_room",
"state": "on"
}
}, check_result)
# Time condition
ha.send_command({
"type": "test_condition",
"condition": {
"condition": "time",
"after": "18:00:00",
"before": "23:00:00"
}
}, check_result)
# Template condition
ha.send_command({
"type": "test_condition",
"condition": {
"condition": "template",
"value_template": "{{ is_state('sun.sun', 'above_horizon') }}"
}
}, check_result)
# And/Or conditions
ha.send_command({
"type": "test_condition",
"condition": {
"condition": "and",
"conditions": [
{
"condition": "state",
"entity_id": "binary_sensor.motion",
"state": "on"
},
{
"condition": "numeric_state",
"entity_id": "sensor.light_level",
"below": 100
}
]
}
}, check_result)
```
### subscribe_entities - Monitor All Entities
Subscribe to get real-time updates for all entity states. Useful for dashboards or monitoring applications.
```python
def on_entities_update(message):
# Get the event with updated entities
if message.get("type") == "event":
event = message.get("event", {})
entities = event.get("a", {}) # 'a' contains added/updated entities
for entity_id, entity_data in entities.items():
print(f"{entity_id}: {entity_data.get('s')} ({entity_data.get('a', {})})")
ha.send_command({
"type": "subscribe_entities"
}, on_entities_update)
```
**Note**: For Python, you'll need to manually track the entity state map. For Node.js, `home-assistant-js-websocket` provides a built-in helper that maintains this for you.
### Registry Information
Get information about devices, areas, and floors:
```python
def on_registry_response(message):
items = message.get("result", [])
for item in items:
print(item)
# Get entity registry
ha.send_command({
"type": "config/entity_registry/list"
}, on_registry_response)
# Get device registry
ha.send_command({
"type": "config/device_registry/list"
}, on_registry_response)
# Get area registry
ha.send_command({
"type": "config/area_registry/list"
}, on_registry_response)
# Get floor registry
ha.send_command({
"type": "config/floor_registry/list"
}, on_registry_response)
```
## REST API (Optional)
**Note**: For most use cases, prefer the WebSocket API above. Use REST API only for simple queries or when WebSocket is not available.
### Connection Validation
Validate connection and get Home Assistant version:
```python
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "httpx>=0.27.0",
# ]
# ///
import httpx
url = "http://homeassistant.local:8123"
token = "YOUR_LONG_LIVED_ACCESS_TOKEN"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Get configuration and version
response = httpx.get(f"{url}/api/config", headers=headers)
config = response.json()
print(f"Home Assistant Version: {config['version']}")
print(f"Location: {config['location_name']}")
print(f"Time Zone: {config['time_zone']}")
```
### Basic Queries
Simple REST queries for when you don't need real-time updates:
```python
import httpx
# Get all entity states
response = httpx.get(f"{url}/api/states", headers=headers)
states = response.json()
for state in states:
print(f"{state['entity_id']}: {state['state']}")
# Get specific entity state
entity_id = "light.living_room"
response = httpx.get(f"{url}/api/states/{entity_id}", headers=headers)
state = response.json()
print(f"State: {state['state']}")
print(f"Attributes: {state['attributes']}")
# Call a service (prefer execute_script via WebSocket instead)
response = httpx.post(
f"{url}/api/services/light/turn_on",
headers=headers,
json={
"entity_id": "light.living_room",
"brightness": 255
}
)
print(f"Service called: {response.json()}")
# Get history
from datetime import datetime, timedelta
end_time = datetime.now()
start_time = end_time - timedelta(hours=1)
response = httpx.get(
f"{url}/api/history/period/{start_time.isoformat()}",
headers=headers,
params={"filter_entity_id": "sensor.temperature"}
)
history = response.json()
```
**Error handling with httpx**:
```python
import httpx
try:
response = httpx.get(f"{url}/api/config", headers=headers, timeout=10.0)
response.raise_for_status()
config = response.json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
print("Authentication failed - check your token")
elif e.response.status_code == 404:
print("Endpoint not found")
else:
print(f"HTTP error: {e.response.status_code}")
except httpx.TimeoutException:
print("Request timed out")
except httpx.RequestError as e:
print(f"Connection failed: {e}")
```
## PEP 723 Inline Script Metadata
When creating standalone Python scripts for users, always include inline script metadata at the top of the file using PEP 723 format. This allows tools like `uv` and `pipx` to automatically manage dependencies.
### Format
```python
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "websocket-client>=1.6.0",
# "httpx>=0.27.0",
# ]
# ///
```
### Running Scripts
Users can run scripts with PEP 723 metadata using:
```bash
# Using uv (recommended)
uv run script.py
# Using pipx
pipx run script.py
# Traditional approach
pip install websocket-client httpx
python script.py
```
## Official Documentation
For complete API documentation, see:
- https://developers.home-assistant.io/docs/api/websocket/
- https://developers.home-assistant.io/docs/api/rest/