[ci skip] Add ha-sofia Home Assistant deployment to skills
- Update home-assistant skill to v2.0.0 covering both ha-london and ha-sofia - Add separate API script for ha-sofia (home-assistant-sofia.py) - ha-sofia: SSH via vbarzin@ha-sofia.viktorbarzin.lan, config at /config/ - Update CLAUDE.md with both HA deployments
This commit is contained in:
parent
a26fdd27b2
commit
191c760b94
3 changed files with 452 additions and 14 deletions
|
|
@ -356,9 +356,10 @@ jellyfin, jellyseerr, tdarr, affine
|
||||||
|
|
||||||
### Home Assistant
|
### Home Assistant
|
||||||
- **Default smart home**: Home Assistant (always use for smart home control)
|
- **Default smart home**: Home Assistant (always use for smart home control)
|
||||||
- **HA URL**: `https://ha-london.viktorbarzin.me`
|
- **Two deployments**:
|
||||||
- **Script**: `.claude/home-assistant.py`
|
- **ha-london** (default): `https://ha-london.viktorbarzin.me` | Script: `.claude/home-assistant.py`
|
||||||
- **Aliases**: "ha" or "HA" = Home Assistant
|
- **ha-sofia**: `https://ha-sofia.viktorbarzin.me` | Script: `.claude/home-assistant-sofia.py` | SSH: `ssh vbarzin@ha-sofia.viktorbarzin.lan` (resolve via `192.168.1.2`), config at `/config/`
|
||||||
|
- **Aliases**: "ha" or "HA" = ha-london. "ha sofia" or "ha-sofia" = ha-sofia.
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
- **Frontend framework**: Svelte (user is learning it, so use Svelte for all new web apps)
|
- **Frontend framework**: Svelte (user is learning it, so use Svelte for all new web apps)
|
||||||
|
|
|
||||||
373
.claude/home-assistant-sofia.py
Normal file
373
.claude/home-assistant-sofia.py
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Home Assistant API Script (ha-sofia instance)
|
||||||
|
Control and query Home Assistant entities on ha-sofia.viktorbarzin.me.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ImportError:
|
||||||
|
print("ERROR: Required package not installed. Run:")
|
||||||
|
print(" pip install requests")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Configuration from environment variables (ha-sofia specific)
|
||||||
|
HA_URL = os.environ.get("HOME_ASSISTANT_SOFIA_URL", "").rstrip("/")
|
||||||
|
HA_TOKEN = os.environ.get("HOME_ASSISTANT_SOFIA_TOKEN")
|
||||||
|
|
||||||
|
if not HA_URL or not HA_TOKEN:
|
||||||
|
print("ERROR: HOME_ASSISTANT_SOFIA_URL and HOME_ASSISTANT_SOFIA_TOKEN environment variables must be set.")
|
||||||
|
print("These should be set when activating the Claude venv (~/.venvs/claude)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bearer {HA_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def api_get(endpoint):
|
||||||
|
"""Make GET request to HA API."""
|
||||||
|
url = f"{HA_URL}/api/{endpoint}"
|
||||||
|
response = requests.get(url, headers=HEADERS, timeout=30)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def api_post(endpoint, data=None):
|
||||||
|
"""Make POST request to HA API."""
|
||||||
|
url = f"{HA_URL}/api/{endpoint}"
|
||||||
|
response = requests.post(url, headers=HEADERS, json=data or {}, timeout=30)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json() if response.text else {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_states():
|
||||||
|
"""Get all entity states."""
|
||||||
|
return api_get("states")
|
||||||
|
|
||||||
|
|
||||||
|
def get_state(entity_id):
|
||||||
|
"""Get state of a specific entity."""
|
||||||
|
return api_get(f"states/{entity_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_services():
|
||||||
|
"""Get all available services."""
|
||||||
|
return api_get("services")
|
||||||
|
|
||||||
|
|
||||||
|
def call_service(domain, service, entity_id=None, data=None):
|
||||||
|
"""Call a Home Assistant service."""
|
||||||
|
payload = data or {}
|
||||||
|
if entity_id:
|
||||||
|
payload["entity_id"] = entity_id
|
||||||
|
return api_post(f"services/{domain}/{service}", payload)
|
||||||
|
|
||||||
|
|
||||||
|
def list_entities(domain_filter=None, area_filter=None):
|
||||||
|
"""List all entities, optionally filtered by domain or area."""
|
||||||
|
states = get_states()
|
||||||
|
entities = []
|
||||||
|
|
||||||
|
for state in states:
|
||||||
|
entity_id = state["entity_id"]
|
||||||
|
domain = entity_id.split(".")[0]
|
||||||
|
|
||||||
|
if domain_filter and domain != domain_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
|
entities.append({
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"state": state["state"],
|
||||||
|
"friendly_name": state["attributes"].get("friendly_name", entity_id),
|
||||||
|
"domain": domain,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sort by domain, then entity_id
|
||||||
|
entities.sort(key=lambda x: (x["domain"], x["entity_id"]))
|
||||||
|
return entities
|
||||||
|
|
||||||
|
|
||||||
|
def turn_on(entity_id):
|
||||||
|
"""Turn on an entity."""
|
||||||
|
domain = entity_id.split(".")[0]
|
||||||
|
return call_service(domain, "turn_on", entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
def turn_off(entity_id):
|
||||||
|
"""Turn off an entity."""
|
||||||
|
domain = entity_id.split(".")[0]
|
||||||
|
return call_service(domain, "turn_off", entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle(entity_id):
|
||||||
|
"""Toggle an entity."""
|
||||||
|
domain = entity_id.split(".")[0]
|
||||||
|
return call_service(domain, "toggle", entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
def set_value(entity_id, value):
|
||||||
|
"""Set value for input entities (input_number, input_text, etc.)."""
|
||||||
|
domain = entity_id.split(".")[0]
|
||||||
|
|
||||||
|
if domain == "input_number":
|
||||||
|
return call_service(domain, "set_value", entity_id, {"value": float(value)})
|
||||||
|
elif domain == "input_text":
|
||||||
|
return call_service(domain, "set_value", entity_id, {"value": str(value)})
|
||||||
|
elif domain == "input_boolean":
|
||||||
|
if value.lower() in ("true", "on", "1", "yes"):
|
||||||
|
return turn_on(entity_id)
|
||||||
|
else:
|
||||||
|
return turn_off(entity_id)
|
||||||
|
elif domain == "input_select":
|
||||||
|
return call_service(domain, "select_option", entity_id, {"option": str(value)})
|
||||||
|
elif domain == "light":
|
||||||
|
# Assume value is brightness percentage
|
||||||
|
return call_service(domain, "turn_on", entity_id, {"brightness_pct": int(value)})
|
||||||
|
elif domain == "climate":
|
||||||
|
return call_service(domain, "set_temperature", entity_id, {"temperature": float(value)})
|
||||||
|
elif domain == "cover":
|
||||||
|
return call_service(domain, "set_cover_position", entity_id, {"position": int(value)})
|
||||||
|
else:
|
||||||
|
print(f"Warning: set_value not implemented for domain '{domain}'", file=sys.stderr)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def run_script(script_id):
|
||||||
|
"""Run a script."""
|
||||||
|
if not script_id.startswith("script."):
|
||||||
|
script_id = f"script.{script_id}"
|
||||||
|
return call_service("script", "turn_on", script_id)
|
||||||
|
|
||||||
|
|
||||||
|
def run_scene(scene_id):
|
||||||
|
"""Activate a scene."""
|
||||||
|
if not scene_id.startswith("scene."):
|
||||||
|
scene_id = f"scene.{scene_id}"
|
||||||
|
return call_service("scene", "turn_on", scene_id)
|
||||||
|
|
||||||
|
|
||||||
|
def send_notification(message, title=None, target="notify"):
|
||||||
|
"""Send a notification."""
|
||||||
|
data = {"message": message}
|
||||||
|
if title:
|
||||||
|
data["title"] = title
|
||||||
|
return call_service("notify", target, data=data)
|
||||||
|
|
||||||
|
|
||||||
|
def format_entities(entities, output_format="text"):
|
||||||
|
"""Format entities for display."""
|
||||||
|
if output_format == "json":
|
||||||
|
return json.dumps(entities, indent=2)
|
||||||
|
|
||||||
|
if not entities:
|
||||||
|
return "No entities found."
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
current_domain = None
|
||||||
|
|
||||||
|
for entity in entities:
|
||||||
|
if entity["domain"] != current_domain:
|
||||||
|
current_domain = entity["domain"]
|
||||||
|
lines.append(f"\n## {current_domain}")
|
||||||
|
|
||||||
|
state = entity["state"]
|
||||||
|
name = entity["friendly_name"]
|
||||||
|
eid = entity["entity_id"]
|
||||||
|
|
||||||
|
# Color-code common states
|
||||||
|
if state in ("on", "home", "open", "playing"):
|
||||||
|
state_display = f"[ON] {state}"
|
||||||
|
elif state in ("off", "away", "closed", "idle", "paused"):
|
||||||
|
state_display = f"[--] {state}"
|
||||||
|
elif state == "unavailable":
|
||||||
|
state_display = "[??] unavailable"
|
||||||
|
else:
|
||||||
|
state_display = state
|
||||||
|
|
||||||
|
lines.append(f"- {name}: {state_display}")
|
||||||
|
lines.append(f" `{eid}`")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def search_entities(query):
|
||||||
|
"""Search entities by name or ID."""
|
||||||
|
query = query.lower()
|
||||||
|
states = get_states()
|
||||||
|
matches = []
|
||||||
|
|
||||||
|
for state in states:
|
||||||
|
entity_id = state["entity_id"]
|
||||||
|
friendly_name = state["attributes"].get("friendly_name", "").lower()
|
||||||
|
|
||||||
|
if query in entity_id.lower() or query in friendly_name:
|
||||||
|
matches.append({
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"state": state["state"],
|
||||||
|
"friendly_name": state["attributes"].get("friendly_name", entity_id),
|
||||||
|
"domain": entity_id.split(".")[0],
|
||||||
|
})
|
||||||
|
|
||||||
|
matches.sort(key=lambda x: (x["domain"], x["entity_id"]))
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Control Home Assistant (ha-sofia)")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
||||||
|
|
||||||
|
# List command
|
||||||
|
list_parser = subparsers.add_parser("list", help="List entities")
|
||||||
|
list_parser.add_argument("--domain", "-d", help="Filter by domain (light, switch, sensor, etc.)")
|
||||||
|
list_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||||
|
|
||||||
|
# Search command
|
||||||
|
search_parser = subparsers.add_parser("search", help="Search entities")
|
||||||
|
search_parser.add_argument("query", help="Search query")
|
||||||
|
search_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||||
|
|
||||||
|
# State command
|
||||||
|
state_parser = subparsers.add_parser("state", help="Get entity state")
|
||||||
|
state_parser.add_argument("entity_id", help="Entity ID")
|
||||||
|
state_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||||
|
|
||||||
|
# On command
|
||||||
|
on_parser = subparsers.add_parser("on", help="Turn on entity")
|
||||||
|
on_parser.add_argument("entity_id", help="Entity ID")
|
||||||
|
|
||||||
|
# Off command
|
||||||
|
off_parser = subparsers.add_parser("off", help="Turn off entity")
|
||||||
|
off_parser.add_argument("entity_id", help="Entity ID")
|
||||||
|
|
||||||
|
# Toggle command
|
||||||
|
toggle_parser = subparsers.add_parser("toggle", help="Toggle entity")
|
||||||
|
toggle_parser.add_argument("entity_id", help="Entity ID")
|
||||||
|
|
||||||
|
# Set command
|
||||||
|
set_parser = subparsers.add_parser("set", help="Set entity value")
|
||||||
|
set_parser.add_argument("entity_id", help="Entity ID")
|
||||||
|
set_parser.add_argument("value", help="Value to set")
|
||||||
|
|
||||||
|
# Script command
|
||||||
|
script_parser = subparsers.add_parser("script", help="Run a script")
|
||||||
|
script_parser.add_argument("script_id", help="Script ID (with or without 'script.' prefix)")
|
||||||
|
|
||||||
|
# Scene command
|
||||||
|
scene_parser = subparsers.add_parser("scene", help="Activate a scene")
|
||||||
|
scene_parser.add_argument("scene_id", help="Scene ID (with or without 'scene.' prefix)")
|
||||||
|
|
||||||
|
# Service command
|
||||||
|
service_parser = subparsers.add_parser("service", help="Call a service")
|
||||||
|
service_parser.add_argument("domain", help="Service domain")
|
||||||
|
service_parser.add_argument("service", help="Service name")
|
||||||
|
service_parser.add_argument("--entity", "-e", help="Entity ID")
|
||||||
|
service_parser.add_argument("--data", "-d", help="JSON data")
|
||||||
|
|
||||||
|
# Services list command
|
||||||
|
services_parser = subparsers.add_parser("services", help="List available services")
|
||||||
|
services_parser.add_argument("--domain", "-d", help="Filter by domain")
|
||||||
|
services_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||||
|
|
||||||
|
# Notify command
|
||||||
|
notify_parser = subparsers.add_parser("notify", help="Send notification")
|
||||||
|
notify_parser.add_argument("message", help="Notification message")
|
||||||
|
notify_parser.add_argument("--title", "-t", help="Notification title")
|
||||||
|
notify_parser.add_argument("--target", default="notify", help="Notification target (default: notify)")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.command:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.command == "list":
|
||||||
|
entities = list_entities(domain_filter=args.domain)
|
||||||
|
output_format = "json" if args.json else "text"
|
||||||
|
print(format_entities(entities, output_format))
|
||||||
|
|
||||||
|
elif args.command == "search":
|
||||||
|
entities = search_entities(args.query)
|
||||||
|
output_format = "json" if args.json else "text"
|
||||||
|
print(format_entities(entities, output_format))
|
||||||
|
|
||||||
|
elif args.command == "state":
|
||||||
|
state = get_state(args.entity_id)
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(state, indent=2))
|
||||||
|
else:
|
||||||
|
print(f"Entity: {state['entity_id']}")
|
||||||
|
print(f"State: {state['state']}")
|
||||||
|
print(f"Name: {state['attributes'].get('friendly_name', 'N/A')}")
|
||||||
|
if state['attributes']:
|
||||||
|
print("Attributes:")
|
||||||
|
for key, value in state['attributes'].items():
|
||||||
|
if key != 'friendly_name':
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
|
||||||
|
elif args.command == "on":
|
||||||
|
turn_on(args.entity_id)
|
||||||
|
print(f"Turned on: {args.entity_id}")
|
||||||
|
|
||||||
|
elif args.command == "off":
|
||||||
|
turn_off(args.entity_id)
|
||||||
|
print(f"Turned off: {args.entity_id}")
|
||||||
|
|
||||||
|
elif args.command == "toggle":
|
||||||
|
toggle(args.entity_id)
|
||||||
|
print(f"Toggled: {args.entity_id}")
|
||||||
|
|
||||||
|
elif args.command == "set":
|
||||||
|
set_value(args.entity_id, args.value)
|
||||||
|
print(f"Set {args.entity_id} to {args.value}")
|
||||||
|
|
||||||
|
elif args.command == "script":
|
||||||
|
run_script(args.script_id)
|
||||||
|
print(f"Ran script: {args.script_id}")
|
||||||
|
|
||||||
|
elif args.command == "scene":
|
||||||
|
run_scene(args.scene_id)
|
||||||
|
print(f"Activated scene: {args.scene_id}")
|
||||||
|
|
||||||
|
elif args.command == "service":
|
||||||
|
data = json.loads(args.data) if args.data else None
|
||||||
|
call_service(args.domain, args.service, args.entity, data)
|
||||||
|
print(f"Called {args.domain}.{args.service}")
|
||||||
|
|
||||||
|
elif args.command == "services":
|
||||||
|
services = get_services()
|
||||||
|
if args.domain:
|
||||||
|
services = [s for s in services if s["domain"] == args.domain]
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(services, indent=2))
|
||||||
|
else:
|
||||||
|
for svc in services:
|
||||||
|
print(f"\n## {svc['domain']}")
|
||||||
|
for name, info in svc["services"].items():
|
||||||
|
desc = info.get("description", "")
|
||||||
|
print(f"- {name}: {desc[:60]}...")
|
||||||
|
|
||||||
|
elif args.command == "notify":
|
||||||
|
send_notification(args.message, args.title, args.target)
|
||||||
|
print(f"Sent notification: {args.message[:50]}...")
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
print(f"HTTP Error: {e}", file=sys.stderr)
|
||||||
|
print(f"Response: {e.response.text}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -8,10 +8,11 @@ description: |
|
||||||
(4) User asks to run a scene or script,
|
(4) User asks to run a scene or script,
|
||||||
(5) User asks "what devices are on?" or "is the door locked?",
|
(5) User asks "what devices are on?" or "is the door locked?",
|
||||||
(6) User mentions smart home, IoT, or home automation.
|
(6) User mentions smart home, IoT, or home automation.
|
||||||
|
There are TWO Home Assistant deployments: ha-london (default) and ha-sofia.
|
||||||
Always use Home Assistant for smart home control.
|
Always use Home Assistant for smart home control.
|
||||||
author: Claude Code
|
author: Claude Code
|
||||||
version: 1.0.0
|
version: 2.0.0
|
||||||
date: 2025-01-25
|
date: 2026-02-07
|
||||||
---
|
---
|
||||||
|
|
||||||
# Home Assistant Control
|
# Home Assistant Control
|
||||||
|
|
@ -26,22 +27,42 @@ Need to control smart home devices, check sensor states, or run automations via
|
||||||
- User mentions turning things on/off
|
- User mentions turning things on/off
|
||||||
- User asks about smart home devices
|
- User asks about smart home devices
|
||||||
|
|
||||||
|
## Deployments
|
||||||
|
|
||||||
|
There are **two** Home Assistant instances:
|
||||||
|
|
||||||
|
| Instance | URL | SSH | Default? |
|
||||||
|
|----------|-----|-----|----------|
|
||||||
|
| **ha-london** | `https://ha-london.viktorbarzin.me` | N/A (runs on K8s cluster) | Yes |
|
||||||
|
| **ha-sofia** | `https://ha-sofia.viktorbarzin.me` | `ssh vbarzin@ha-sofia.viktorbarzin.lan` (resolve via `192.168.1.2`) | No |
|
||||||
|
|
||||||
|
- **Default**: ha-london (use unless user specifies "sofia" or "ha-sofia")
|
||||||
|
- **Aliases**: "ha" or "HA" = ha-london. "ha sofia" or "ha-sofia" = ha-sofia.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
- The `~/.venvs/claude` virtualenv must have `requests` package installed
|
- The `~/.venvs/claude` virtualenv must have `requests` package installed
|
||||||
- Environment variables `HOME_ASSISTANT_URL` and `HOME_ASSISTANT_TOKEN` must be set in the venv activation script
|
- Environment variables for each instance must be set in the venv activation script:
|
||||||
|
- **ha-london**: `HOME_ASSISTANT_URL` and `HOME_ASSISTANT_TOKEN`
|
||||||
|
- **ha-sofia**: `HOME_ASSISTANT_SOFIA_URL` and `HOME_ASSISTANT_SOFIA_TOKEN`
|
||||||
|
|
||||||
## Solution
|
## API Control
|
||||||
|
|
||||||
### Script Location
|
### Scripts
|
||||||
```
|
|
||||||
/home/wizard/code/infra/.claude/home-assistant.py
|
| Instance | Script |
|
||||||
```
|
|----------|--------|
|
||||||
|
| ha-london | `.claude/home-assistant.py` |
|
||||||
|
| ha-sofia | `.claude/home-assistant-sofia.py` |
|
||||||
|
|
||||||
### Execution Pattern (CRITICAL)
|
### Execution Pattern (CRITICAL)
|
||||||
Always activate the venv to get environment variables:
|
Always activate the venv to get environment variables:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# ha-london (default)
|
||||||
source ~/.venvs/claude/bin/activate && cd ~/code/infra && python .claude/home-assistant.py [command] [options]
|
source ~/.venvs/claude/bin/activate && cd ~/code/infra && python .claude/home-assistant.py [command] [options]
|
||||||
|
|
||||||
|
# ha-sofia
|
||||||
|
source ~/.venvs/claude/bin/activate && cd ~/code/infra && python .claude/home-assistant-sofia.py [command] [options]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Available Commands
|
### Available Commands
|
||||||
|
|
@ -129,14 +150,56 @@ python .claude/home-assistant.py notify "Motion detected" --title "Security Aler
|
||||||
python .claude/home-assistant.py notify "Hello" --target notify.mobile_app
|
python .claude/home-assistant.py notify "Hello" --target notify.mobile_app
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSH Access (ha-sofia only)
|
||||||
|
|
||||||
|
ha-sofia supports SSH for direct configuration management.
|
||||||
|
|
||||||
|
### Connection
|
||||||
|
```bash
|
||||||
|
# DNS resolves via 192.168.1.2
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan
|
||||||
|
```
|
||||||
|
|
||||||
|
If DNS resolution fails (e.g., not on the local network), use the DNS server directly:
|
||||||
|
```bash
|
||||||
|
ssh vbarzin@$(dig +short ha-sofia.viktorbarzin.lan @192.168.1.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Path
|
||||||
|
```
|
||||||
|
/config/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common SSH Tasks
|
||||||
|
```bash
|
||||||
|
# Edit configuration
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan "cat /config/configuration.yaml"
|
||||||
|
|
||||||
|
# Check HA logs
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan "cat /config/home-assistant.log | tail -50"
|
||||||
|
|
||||||
|
# List automations
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan "ls /config/automations.yaml"
|
||||||
|
|
||||||
|
# Restart HA (after config changes)
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan "ha core restart"
|
||||||
|
|
||||||
|
# Check config validity
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan "ha core check"
|
||||||
|
```
|
||||||
|
|
||||||
## Complete Example
|
## Complete Example
|
||||||
|
|
||||||
To turn on the living room light:
|
To turn on the living room light on ha-london:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source ~/.venvs/claude/bin/activate && cd ~/code/infra && python .claude/home-assistant.py on light.living_room
|
source ~/.venvs/claude/bin/activate && cd ~/code/infra && python .claude/home-assistant.py on light.living_room
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To check ha-sofia configuration:
|
||||||
|
```bash
|
||||||
|
ssh vbarzin@ha-sofia.viktorbarzin.lan "cat /config/configuration.yaml"
|
||||||
|
```
|
||||||
|
|
||||||
## Common Entity Domains
|
## Common Entity Domains
|
||||||
|
|
||||||
| Domain | Description | Common Actions |
|
| Domain | Description | Common Actions |
|
||||||
|
|
@ -175,4 +238,5 @@ source ~/.venvs/claude/bin/activate && cd ~/code/infra && python .claude/home-as
|
||||||
1. **Entity IDs are case-sensitive** - use `search` to find exact IDs
|
1. **Entity IDs are case-sensitive** - use `search` to find exact IDs
|
||||||
2. **Token must have sufficient permissions** - ensure token has access to all entities
|
2. **Token must have sufficient permissions** - ensure token has access to all entities
|
||||||
3. **Some entities require specific data** - use `services` command to see required fields
|
3. **Some entities require specific data** - use `services` command to see required fields
|
||||||
4. **HA URL**: `https://ha-london.viktorbarzin.me`
|
4. **Two instances**: ha-london (default, K8s), ha-sofia (SSH + API)
|
||||||
|
5. **ha-sofia SSH**: Uses default SSH key, user `vbarzin`, resolve DNS via `192.168.1.2`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue