Files
hermes-config/skills/smart-home/homeassistant-integration/SKILL.md
T
2026-07-12 10:17:17 -04:00

11 KiB
Raw Blame History

name, description, version, author, platforms, created_by, metadata
name description version author platforms created_by metadata
homeassistant-integration Connect Home Assistant to Hermes — token setup, toolset enablement, and smart home control via Hermes tools. 1.2.0 ray
linux
agent
hermes requires
tags
smart-home
homeassistant
integration
ha
env_vars toolsets gateway_restart
HASS_TOKEN
HASS_URL
homeassistant
true

Home Assistant + Hermes Integration

Connect a Home Assistant instance to Hermes so the agent can list entities, read sensor states, discover services, and control devices (lights, switches, climate, media players, etc.) via the homeassistant toolset.

Prerequisites

  • A running Home Assistant instance (local network or remote)
  • A Long-Lived Access Token from your HA profile page (click your username → Security → Long-Lived Access Tokens)
  • The HA server accessible from the Hermes host

Setup

1. Get a Long-Lived Access Token

In Home Assistant: click your user profile (bottom-left) → SecurityLong-Lived Access TokensCreate Token.

The token is a JWT that looks like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOi...<rest of token>

You can decode it to verify:

python3 -c "
import base64, json
payload = base64.urlsafe_b64decode('PAYLOAD_SECTION' + '==')
data = json.loads(payload)
print(f'Issuer: {data[\"iss\"]}')
print(f'Expires: {data[\"exp\"]}')
"

HA long-lived access tokens typically expire in 10 years.

2. Set the Environment Variables

hermes config set HASS_URL http://YOUR_HA_HOST:8123
hermes config set HASS_TOKEN <your-long-lived-access-token>

hermes config set auto-detects secrets — the URL goes to config.yaml, the token goes to .env. The tool code reads both from environment variables, so if HASS_URL landed in config.yaml instead of .env, add it to .env as well:

echo 'HASS_URL=http://YOUR_HA_HOST:8123' >> ~/.hermes/.env

Verify they're in place:

grep HASS ~/.hermes/.env

3. Enable the Toolset

hermes tools enable homeassistant

Verify:

hermes tools list | grep homeassistant
# ✓ enabled  homeassistant  🏠 Home Assistant

4. Verify the Token Works

Test with a direct API call before restarting the gateway:

python3 -c "
import json, urllib.request, subprocess
token = subprocess.run(['grep', 'HASS_TOKEN', '$HOME/.hermes/.env'],
    capture_output=True, text=True).stdout.strip().split('=',1)[1]
req = urllib.request.Request('http://YOUR_HA_HOST:8123/api/',
    headers={'Authorization': f'Bearer {token}'})
data = json.loads(urllib.request.urlopen(req, timeout=10).read())
print(f'HA: {data.get(\"location_name\", \"unnamed\")}')
"

Also check entity count:

# Same pattern, hit /api/states instead and count by domain

If you get HTTP 200, the token is valid and the URL is reachable.

5. Restart the Gateway

Tool changes require a new session. Restart the gateway:

hermes gateway restart

After restart, the agent has access to four HA tools:

Tool Purpose
ha_list_entities List/filter entities by domain or area
ha_get_state Get detailed state + attributes of one entity
ha_list_services Discover available services (actions) per domain
ha_call_service Control a device (turn_on, turn_off, set_temperature, etc.)

Security

The homeassistant_tool.py implementation blocks these high-risk service domains for safety: shell_command, command_line, python_script, pyscript, hassio, rest_command

Automation Patterns

Cron-Based Condition Monitoring

Use Hermes cron jobs to poll an HA entity and act when a condition is met (e.g., turn off AC when temp drops to target). The cron job should self-terminate after acting.

Recipe — trigger action on sensor threshold:

cronjob(action='create',
  name='<descriptive name>',
  prompt='Check <entity_id> using ha_get_state. If <condition>, call ha_call_service to <action> and report success, then use cronjob to remove this monitoring job (job_id in context). If condition not met, report current state briefly and do nothing.',
  schedule='*/10 22-23,0-2 * * *',   # every 10 min from 10pm2am (use CRON EXPRESSION, not "10m" — that's a one-shot)
  repeat=20,
  toolsets=['homeassistant', 'cronjob'])

Key details:

  • toolsets: Must include both homeassistant and cronjob so the cron agent can check sensors, call services, and remove itself.
  • Schedule: Use */N hour-range * * * for time-windowed polling (e.g., */7 22-23,0-2 * * * = every 7 minutes from 10 PM through 2 AM). Avoid starting monitoring before the user wants it — use the cron expression to delay first run.
  • CRITICAL — schedule format: Simple time strings like "10m" or "30m" are parsed as one-shot ("once in 10 minutes"), NOT recurring. The cron will run exactly once and then stop. Always use cron expressions (*/10 * * * *) or "every 10m" for recurring jobs. A job that silently runs once and completes without acting is the #1 failure mode for HVAC monitoring.
  • Repeat limit: Cap with repeat to prevent indefinite polling. With N-minute intervals, repeat=20 covers ~N×20 minutes.
  • Prompt must include self-removal: The job should call cronjob(action='remove', job_id=...) after acting so it doesn't keep polling indefinitely.
  • Schedule corrections: Users often tweak poll intervals or start times after creation. Use cronjob(action='update', job_id=..., schedule=...) rather than deleting and recreating.

Pitfalls

  • Home Assistant behind VPS reverse proxy: When proxying HA through an external nginx (VPS + Tailscale), add the proxy's Tailscale IP to trusted_proxies in configuration.yaml or HA returns 400:
    http:
      use_x_forwarded_for: true
      trusted_proxies:
        - 127.0.0.1
        - 100.86.68.23   # VPS Tailscale IP
    
    Restart: docker restart homeassistant. See docker-service-deployment skill, references/vps-migration.md for the full VPS setup pattern.
  • CRITICAL — Cron schedule format: Bare duration strings like "10m" or "30m" are ONE-SHOT ("once in X minutes"). The job runs exactly once and silently completes — the #1 failure mode for HVAC monitoring. Always use cron expressions: "*/10 * * * *" (every 10 minutes), "*/7 22-23,0-2 * * *" (time-windowed). Alternative: "every 10m" shorthand. Verify with cronjob(action='list') after creation — "once in Nm" means it won't repeat.
  • Climate vs sensor entities: Climate entities (climate.upstairs) are the thermostat — call ha_call_service to control them. Sensor entities (sensor.upstairs_temperature) are read-only readings. For threshold monitoring, check the sensor for the reading but control the climate entity.
  • HASS_URL must be in .env: The tool reads os.getenv("HASS_URL"), not config.yaml. Add it to .env explicitly.
  • Docs 404: The Hermes docs page for Home Assistant integration returns 404. Canonical reference: tools/homeassistant_tool.py and gateway/config.py.
  • Subprocesses don't inherit .env: When testing with python3 -c, read secrets via subprocess.run(['grep', ...]) instead of os.getenv().
  • Gateway restart required: Toolset changes need hermes gateway restart.
  • Internal vs proxy URL: The tool needs the direct API URL (e.g., http://192.168.50.98:8123), not the external proxy port.
  • Entity registry may 404: Use /api/config/config_entries/entry instead to discover integrations and device metadata.

Two-Phase Monitoring (Wide → Narrow Polling)

When the target is far away, poll infrequently to save resources. When it nears the threshold, switch to tight polling automatically.

How it works: The cron job starts with a wide interval (e.g., every 30 min). When the sensor value enters a "close" zone, the job updates its own schedule to a tight interval (e.g., every 7 min). When the final threshold is hit, it acts and removes itself.

Recipe:

# Phase 1 — wide polling (every 30 min on the half-hour, 10pm2am)
cronjob(action='create',
  name='Turn off AC when temp hits 78°F',
  prompt='Check climate.upstairs with ha_get_state. '
    'If temp > 79°F: report briefly, do nothing (stay in wide-poll mode). '
    'If temp ≤ 79°F and > 78°F: UPGRADE this job to tight polling — '
    'cronjob(action=update, job_id=THIS_JOB_ID, schedule="*/7 22-23,0-2 * * *"). '\
    'Report "now monitoring closely." '
    'If temp ≤ 78°F: ha_call_service(domain=climate, service=turn_off, '
    'entity_id=climate.upstairs), report success, remove this job.',
  schedule='0,30 22-23,0-2 * * *',
  repeat=20,
  toolsets=['homeassistant','cronjob'])

Once upgraded to Phase 2, the job's prompt should know it's in tight mode: check temp, act if ≤ target, otherwise report and wait. The prompt must handle both modes so it works correctly after a self-upgrade.

Key points:

  • Phase 1 prompt must include the self-upgrade logic (wide → tight) AND the final action logic (act + self-remove).
  • Phase 2 prompt (after upgrade) only needs: check, act-if-condition-met, otherwise report.
  • Use repeat to cap total runs so the job doesn't poll forever if the condition never triggers.
  • Users often specify multiple constraints (interval, start time, threshold-based frequency). Ask clarifying questions if the intent is ambiguous, and prefer the two-phase pattern when the target is expected to drift slowly.

IoT Device Onboarding

For finding, identifying, and adding new WiFi IoT devices to the network and Home Assistant — WiFi setup, IP discovery, MAC vendor lookup, LAN mode enablement, and HA integration — see references/iot-device-onboarding.md.

Identifying Devices & Integrations

Thermostat Model / Integration Discovery

The entity states API (/api/states) doesn't expose manufacturer or model info. To find what integration provides a climate entity and what hardware it's connected to, query the HA config entries API:

import json, urllib.request

# List all config entries (integrations)
req = urllib.request.Request(
    'http://HA_HOST:8123/api/config/config_entries/entry',
    headers={'Authorization': f'Bearer {token}'})
entries = json.loads(urllib.request.urlopen(req).read())

for entry in entries:
    if entry.get('domain') == 'honeywell':  # or 'ecobee', 'nest', etc.
        print(f"Title: {entry['title']}, State: {entry['state']}")

The domain field reveals the integration (e.g., honeywell, ecobee, nest). The entity registry endpoints (/api/config/entity_registry/*) may 404 depending on HA version or reverse-proxy setup — the config entries API is a reliable fallback.