Initial commit
This commit is contained in:
565
skills/home-assistant/references/node_api.md
Normal file
565
skills/home-assistant/references/node_api.md
Normal 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/
|
||||
649
skills/home-assistant/references/python_api.md
Normal file
649
skills/home-assistant/references/python_api.md
Normal 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/
|
||||
Reference in New Issue
Block a user