Skill: nj070574-gif/home-assistant-skill (version 2.1.0)

--- name: home-assistant-skill version: "2.1.0" description: > Control and query Home Assistant via natural language. Covers lights, switches, climate, temperature sensors, cameras, automations, energy monitoring, EV chargers, presence detection, door sensors, and home summaries. Credentials loaded from OpenClaw environment only. author: openclaw-community license: MIT tags: - home-assistant - smart-home - lights - climate - cameras - automation - energy - iot - heating - telegram - latest requires: env: - name: HOME_ASSISTANT_URL description: Your Home Assistant URL (e.g. http://homeassistant.local:8123) - name: HOME_ASSISTANT_TOKEN description: Long-lived access token (HA Profile > Security > Long-Lived Access Tokens) optional_env: - name: HOME_ASSISTANT_SSL_VERIFY description: Set to 'false' if using a self-signed certificate - name: HOME_ASSISTANT_CA_CERT description: Path to CA certificate file python_packages: - requests - urllib3 security: scope: owner-operated note: > Connects only to the Home Assistant instance you configure via HOME_ASSISTANT_URL. All credentials are user-supplied via openclaw.json env block. No data is sent to third parties. credential_handling: user-supplied-only network_access: user-own-home-assistant-only --- # Home Assistant Integration v2.1 — OpenClaw Skill Control and query your Home Assistant smart home in plain English through Telegram or any OpenClaw channel. ## Setup ### 1. Create a Home Assistant Long-Lived Token In Home Assistant: **Profile** (bottom-left) → **Security** → **Long-Lived Access Tokens** → **Create Token** Copy the token immediately — it is only shown once. ### 2. Add credentials to openclaw.json `json { "env": { "HOME_ASSISTANT_URL": "http://homeassistant.local:8123", "HOME_ASSISTANT_TOKEN": "your-long-lived-token-here" } } ` Using HTTPS with a self-signed certificate? Also add: `json "HOME_ASSISTANT_SSL_VERIFY": "false" ` ### 3. Restart OpenClaw ` ash sudo systemctl restart openclaw ` ### 4. Test Send your bot: home summary ## Security Notes - Connects **only** to your configured HOME_ASSISTANT_URL — no third-party calls - Create a dedicated HA user with only the permissions your agent needs - Store credentials in openclaw.json with restricted permissions (chmod 600) - Prefer HOME_ASSISTANT_CA_CERT over HOME_ASSISTANT_SSL_VERIFY=false for HTTPS ## What You Can Ask | Phrase | What happens | |---|---| | home summary | Temperatures, lights on, heating status, active switches | | what is the temperature? | All temperature sensors | | urn off the living room lights | Calls light.turn_off | | set the heating to 21 degrees | Calls climate.set_temperature | | is the EV charger on? | Reads switch state | | show me the front door camera | Returns snapshot URL | | list all automations | Shows enabled/disabled automations | | is anyone home? | Reads presence/person entity states | | what is my energy consumption? | All power/energy sensors | | urn on lights at 80% brightness | Service call with brightness attribute | ## Available Operations The skill provides 15 Python snippets executed via the OpenClaw exec tool: - _load_config — loads credentials from environment (always runs first) - check_api — tests HA connectivity - ha_summary_for_telegram — full home summary - get_temperature_sensors — all temperature sensors - get_lights — lights with brightness levels - get_switches — all switches with state - get_climate — thermostat/climate status - call_service — control any HA device/service - search_entities — find entities by keyword - get_cameras — camera list with snapshot URLs - camera_snapshot — download camera image - get_automations — all automations with last-triggered - rigger_automation — fire a specific automation - get_energy — energy and power sensors - send_notification — send via HA notify service ## Skill File The full skill implementation is in home_assistant.json in this directory. It contains all 15 snippets as Python code that the agent executes via the Home Assistant REST API (/api/states, /api/services/*). ## Troubleshooting **HOME_ASSISTANT_TOKEN not configured** Check the HOME_ASSISTANT_TOKEN in your openclaw.json env block and restart OpenClaw. **401 Unauthorized** Token expired. Regenerate: HA → Profile → Security → Long-Lived Access Tokens. **SSL certificate verify failed** Add "HOME_ASSISTANT_SSL_VERIFY": "false" to your openclaw.json env block. **Connection refused** Check HOME_ASSISTANT_URL is correct and HA is running.

path: home_assistant.json
content: {
  "name": "home_assistant",
  "version": "2.1.0",
  "description": "Home Assistant REST API integration for OpenClaw agents. Query and control your smart home via natural language through Telegram or any OpenClaw channel. Covers: lights, switches, climate/heating, temperature sensors, cameras, automations, energy monitoring, EV chargers, presence detection, door/lock sensors, and home summaries. Credentials are loaded securely from your OpenClaw environment - no tokens in skill files.",
  "notes": "SETUP REQUIRED. See README for full instructions.\nToken is read automatically from (in priority order):\n  1. HOME_ASSISTANT_TOKEN environment variable\n  2. openclaw.json env block: HOME_ASSISTANT_URL + HOME_ASSISTANT_TOKEN\nFor HTTPS with self-signed certs set HOME_ASSISTANT_SSL_VERIFY=false in your openclaw.json env block.\nAll snippets require _load_config to be executed first in the same script.",
  "setup": {
    "step1": "In Home Assistant: Profile (bottom-left) -> Security -> Long-Lived Access Tokens -> Create Token",
    "step2": "Add to ~/.openclaw/openclaw.json env block: HOME_ASSISTANT_URL and HOME_ASSISTANT_TOKEN",
    "step3": "If using HTTPS with self-signed cert also add: HOME_ASSISTANT_SSL_VERIFY=false",
    "step4": "Restart OpenClaw: sudo systemctl restart openclaw",
    "step5": "Test: ask your bot 'home summary' or 'what is the temperature?'"
  },
  "snippets": {
    "_load_config": "import json, requests, urllib3, os\n\ndef _load_ha_config():\n    # Priority 1: system environment variables\n    url   = os.environ.get(\"HOME_ASSISTANT_URL\", \"\").strip()\n    [REDACTED_SECRET]"HOME_ASSISTANT_TOKEN\", \"\").strip()\n    # Priority 2: openclaw.json env block\n    if not token:\n        oc = Path.home() / \".openclaw/openclaw.json\"\n        if oc.exists():\n            try:\n                env = json.loads(oc.read_text()).get(\"env\", {})\n                url   = url   or env.get(\"HOME_ASSISTANT_URL\",   \"\").strip()\n                token = token or env.get(\"HOME_ASSISTANT_TOKEN\", \"\").strip()\n            except Exception: pass\n    # (Token must be set via env var or openclaw.json - see README)\n    if not token:\n        raise RuntimeError(\n            \"HOME_ASSISTANT_TOKEN not configured. \"\n            \"Add to openclaw.json env block, set HOME_ASSISTANT_TOKEN env var, \"\n            \"or create ~/.openclaw/workspace/.secrets/home_assistant.token\"\n        )\n    return (url or \"http://homeassistant.local:8123\").rstrip(\"/\"), token\n\ndef _ssl_verify():\n    v  = os.environ.get(\"HOME_ASSISTANT_SSL_VERIFY\", \"\").lower()\n    ca = os.environ.get(\"HOME_ASSISTANT_CA_CERT\", \"\").strip()\n    if v == \"false\": return False\n    if ca: return ca\n    return True\n\nurllib3.disable_warnings()\nHA_URL, HA_TOKEN = _load_ha_config()\nVERIFY  = _ssl_verify()\nHEADERS = {\"Authorization\": \"Bearer \" + HA_TOKEN, \"Content-Type\": \"application/json\"}\n\ndef ha_get(path):\n    r = requests.get(HA_URL + path, headers=HEADERS, verify=VERIFY, timeout=15)\n    r.raise_for_status(); return r.json()\n\ndef ha_post(path, data=None):\n    r = requests.post(HA_URL + path, headers=HEADERS, json=data or {}, verify=VERIFY, timeout=15)\n    r.raise_for_status(); return r.json()",
    "check_api": "r = requests.get(HA_URL + \"/api/\", headers=HEADERS, verify=VERIFY, timeout=10)\nif r.status_code == 200:\n    print(\"Home Assistant \" + r.json().get(\"version\",\"\") + \" is online at \" + HA_URL)\nelse:\n    print(\"ERROR: HTTP \" + str(r.status_code) + \" - check URL and token\")",
    "ha_summary_for_telegram": "from datetime import datetime\nstates = ha_get(\"/api/states\")\nlines  = [\"Home Summary - \" + datetime.now().strftime(\"%H:%M %d/%m/%Y\")]\n\n# Temperatures\ntemps = []\nfor s in states:\n    a   = s.get(\"attributes\", {})\n    dc  = a.get(\"device_class\",       \"\") or \"\"\n    uom = a.get(\"unit_of_measurement\", \"\") or \"\"\n    if \"temperature\" in dc or chr(176) in uom:\n        try:\n            val  = round(float(s[\"state\"]), 1)\n            name = a.get(\"friendly_name\", s[\"entity_id\"])\n            temps.append(\"  - \" + name + \": \" + str(val) + uom)\n        except Exception: pass\nif temps:\n    lines.append(\"\")\n    lines.append(\"Temperatures:\")\n    lines += temps[:6]\n\n# Lights\nlon = [s for s in states if s[\"entity_id\"].startswith(\"light.\") and s[\"state\"] == \"on\"]\nlines.append(\"\")\nif lon:\n    names = [s[\"attributes\"].get(\"friendly_name\", s[\"entity_id\"]) for s in lon]\n    lines.append(\"Lights ON: \" + \", \".join(names[:5]))\nelse:\n    lines.append(\"Lights: all off\")\n\n# Climate\nfor c in [s for s in states if s[\"entity_id\"].startswith(\"climate.\")]:\n    a      = c.get(\"attributes\", {})\n    name   = a.get(\"friendly_name\", c[\"entity_id\"])\n    curr   = str(a.get(\"current_temperature\", \"?\"))\n    target = str(a.get(\"temperature\",         \"?\"))\n    lines.append(\"\")\n    lines.append(\"Climate \" + name + \": \" + c[\"state\"] + \", \" + curr + \"deg -> \" + target + \"deg\")\n\n# Switches ON\nson = [s for s in states if s[\"entity_id\"].startswith(\"switch.\") and s[\"state\"] == \"on\"]\nif son:\n    names = [s[\"attributes\"].get(\"friendly_name\", s[\"entity_id\"]) for s in son]\n    lines.append(\"\")\n    lines.append(\"Switches ON: \" + \", \".join(names[:5]))\n\nprint(chr(10).join(lines))",
    "get_temperature_sensors": "states = ha_get(\"/api/states\")\ntemps = []\nfor s in states:\n    a   = s.get(\"attributes\", {})\n    dc  = a.get(\"device_class\", \"\") or \"\"\n    uom = a.get(\"unit_of_measurement\", \"\") or \"\"\n    if \"temperature\" in dc or chr(176) in uom or \"temp\" in s[\"entity_id\"].lower():\n        try:\n            val  = round(float(s[\"state\"]), 1)\n            name = a.get(\"friendly_name\", s[\"entity_id\"])\n            temps.append((name, val, uom))\n        except Exception: pass\ntemps.sort()\nprint(\"Temperature sensors (\" + str(len(temps)) + \"):\")\nfor n, v, u in temps: print(\"  \" + n + \": \" + str(v) + u)",
    "get_lights": "states = ha_get(\"/api/states\")\nlights = [s for s in states if s[\"entity_id\"].startswith(\"light.\")]\non  = [l for l in lights if l[\"state\"] == \"on\"]\noff = [l for l in lights if l[\"state\"] == \"off\"]\nprint(\"Lights: \" + str(len(on)) + \" on, \" + str(len(off)) + \" off\")\nfor l in on:\n    name = l[\"attributes\"].get(\"friendly_name\", l[\"entity_id\"])\n    bri  = l[\"attributes\"].get(\"brightness\", \"\")\n    pct  = (\" (\" + str(round(int(bri)/255*100)) + \"%)\" if bri else \"\")\n    print(\"  ON:  \" + name + pct)\nfor l in off[:10]:\n    print(\"  off: \" + l[\"attributes\"].get(\"friendly_name\", l[\"entity_id\"]))",
    "get_switches": "states = ha_get(\"/api/states\")\nswitches = [s for s in states if s[\"entity_id\"].startswith(\"switch.\")]\nprint(\"Switches (\" + str(len(switches)) + \"):\")\nfor sw in switches:\n    icon = \"ON \" if sw[\"state\"] == \"on\" else \"off\"\n    name = sw[\"attributes\"].get(\"friendly_name\", sw[\"entity_id\"])\n    print(\"  \" + icon + \": \" + name)",
    "get_climate": "states = ha_get(\"/api/states\")\nclimates = [s for s in states if s[\"entity_id\"].startswith(\"climate.\")]\nprint(\"Climate devices (\" + str(len(climates)) + \"):\")\nfor c in climates:\n    a      = c.get(\"attributes\", {})\n    name   = a.get(\"friendly_name\", c[\"entity_id\"])\n    curr   = str(a.get(\"current_temperature\", \"?\"))\n    target = str(a.get(\"temperature\", \"?\"))\n    uom    = a.get(\"unit_of_measurement\", \"C\")\n    print(\"  \" + name + \": mode=\" + c[\"state\"] + \", current=\" + curr + uom + \", target=\" + target + uom)",
    "call_service": "# Adjust these three variables for your device:\nDOMAIN    = \"light\"               # light / switch / climate / automation / script\nSERVICE   = \"toggle\"              # turn_on / turn_off / toggle / set_temperature\nENTITY_ID = \"light.living_room\"   # replace with your entity_id\nEXTRA     = {}                    # optional: {\"brightness_pct\": 80} or {\"temperature\": 21}\nresult = ha_post(\"/api/services/\" + DOMAIN + \"/\" + SERVICE,\n                 {\"entity_id\": ENTITY_ID, **EXTRA})\nprint(\"Called \" + DOMAIN + \".\" + SERVICE + \" on \" + ENTITY_ID)\nfor r in result[:5]:\n    print(\"  -> \" + r.get(\"entity_id\", \"\") + \" is now \" + r.get(\"state\", \"\"))",
    "search_entities": "KEYWORD = \"temperature\"   # change to: light / switch / camera / door / lock / battery\nstates  = ha_get(\"/api/states\")\nmatches = [s for s in states\n           if KEYWORD.lower() in s[\"entity_id\"].lower()\n           or KEYWORD.lower() in str(s.get(\"attributes\", {}).get(\"friendly_name\", \"\")).lower()\n           or KEYWORD.lower() in str(s.get(\"attributes\", {}).get(\"device_class\",  \"\")).lower()]\nprint(\"Found \" + str(len(matches)) + \" entities matching: \" + KEYWORD)\nfor s in matches:\n    name = s[\"attributes\"].get(\"friendly_name\", s[\"entity_id\"])\n    uom  = s[\"attributes\"].get(\"unit_of_measurement\", \"\")\n    print(\"  \" + s[\"entity_id\"].ljust(50) + \" \" + s[\"state\"].ljust(12) + \" \" + uom + \" (\" + name + \")\")",
    "get_cameras": "states = ha_get(\"/api/states\")\ncams = [s for s in states if s[\"entity_id\"].startswith(\"camera.\")]\nprint(\"Cameras (\" + str(len(cams)) + \"):\")\nfor c in cams:\n    name = c[\"attributes\"].get(\"friendly_name\", c[\"entity_id\"])\n    snap = HA_URL + \"/api/camera_proxy/\" + c[\"entity_id\"]\n    print(\"  \" + name + \": state=\" + c[\"state\"])\n    print(\"    Snapshot URL: \" + snap)",
    "camera_snapshot": "CAMERA_ENTITY = \"camera.your_camera_name\"  # replace with your camera entity_id\nOUT_PATH      = \"/tmp/ha_snapshot.jpg\"\nimport os\nr = requests.get(HA_URL + \"/api/camera_proxy/\" + CAMERA_ENTITY,\n                 headers=HEADERS, verify=VERIFY, timeout=20)\nr.raise_for_status()\nopen(OUT_PATH, \"wb\").write(r.content)\nprint(\"Snapshot saved: \" + OUT_PATH + \" (\" + str(len(r.content)//1024) + \" KB)\")",
    "get_automations": "states = ha_get(\"/api/states\")\nautos  = [s for s in states if s[\"entity_id\"].startswith(\"automation.\")]\nprint(\"Automations (\" + str(len(autos)) + \"):\")\nfor a in autos:\n    icon = \"ON \" if a[\"state\"] == \"on\" else \"off\"\n    name = a[\"attributes\"].get(\"friendly_name\", a[\"entity_id\"])\n    last = str(a[\"attributes\"].get(\"last_triggered\", \"never\"))\n    print(\"  \" + icon + \": \" + name + \" (last triggered: \" + last + \")\")",
    "trigger_automation": "AUTO_ENTITY = \"automation.your_automation_name\"  # replace with your automation entity_id\nresult = ha_post(\"/api/services/automation/trigger\", {\"entity_id\": AUTO_ENTITY})\nprint(\"Triggered: \" + AUTO_ENTITY)",
    "get_energy": "states = ha_get(\"/api/states\")\nkeys   = [\"power\", \"energy\", \"kwh\", \"watt\", \"solar\", \"battery\", \"voltage\", \"grid\"]\nfound  = []\nfor s in states:\n    eid = s[\"entity_id\"].lower()\n    uom = (s.get(\"attributes\", {}).get(\"unit_of_measurement\", \"\") or \"\").lower()\n    dc  = (s.get(\"attributes\", {}).get(\"device_class\",         \"\") or \"\").lower()\n    if any(k in eid or k in uom or k in dc for k in keys):\n        name = s[\"attributes\"].get(\"friendly_name\", s[\"entity_id\"])\n        uom2 = s[\"attributes\"].get(\"unit_of_measurement\", \"\")\n        found.append((name, s[\"state\"], uom2))\nprint(\"Energy/Power sensors (\" + str(len(found)) + \"):\")\nfor name, val, uom in sorted(found):\n    print(\"  \" + name + \": \" + val + \" \" + uom)",
    "send_notification": "# Requires telegram_bot or other notify integration configured in Home Assistant\nMESSAGE        = \"Alert from OpenClaw\"\nNOTIFY_SERVICE = \"notify.notify\"   # e.g. notify.telegram / notify.mobile_app\nha_post(\"/api/services/\" + NOTIFY_SERVICE.replace(\".\", \"/\"), {\"message\": MESSAGE})\nprint(\"Notification sent via \" + NOTIFY_SERVICE)"
  },
  "triggers": [
    "turn on",
    "turn off",
    "switch on",
    "switch off",
    "toggle",
    "temperature",
    "what is the temperature",
    "how warm",
    "how hot",
    "how cold",
    "humidity",
    "is anyone home",
    "presence",
    "who is home",
    "anyone home",
    "lights",
    "light",
    "lamp",
    "brightness",
    "dim",
    "heating",
    "thermostat",
    "climate",
    "set temperature",
    "hvac",
    "boiler",
    "EV charger",
    "car charger",
    "charging",
    "electric vehicle",
    "camera",
    "cameras",
    "snapshot",
    "doorbell",
    "motion",
    "alarm",
    "armed",
    "disarmed",
    "security",
    "door",
    "lock",
    "unlock",
    "garage",
    "cover",
    "blinds",
    "curtains",
    "energy",
    "power",
    "solar",
    "battery",
    "consumption",
    "electricity",
    "weather",
    "forecast",
    "outside temperature",
    "scene",
    "automation",
    "routine",
    "run script",
    "fan",
    "ventilation",
    "air",
    "smoke",
    "CO2",
    "air quality",
    "leak",
    "moisture",
    "flood",
    "media",
    "TV",
    "speaker",
    "volume",
    "play",
    "pause",
    "home summary",
    "home status",
    "house status",
    "status report",
    "home assistant",
    "smart home",
    "HA",
    "list entities",
    "list devices",
    "list sensors",
    "list lights",
    "switches",
    "switch status",
    "notify",
    "alert",
    "send notification"
  ],
  "usage_examples": [
    "Home summary",
    "What is the temperature?",
    "Turn off all the lights",
    "Is the EV charger on?",
    "Show me the front door camera",
    "Set the heating to 21 degrees",
    "List all my automations",
    "Is anyone home?",
    "Turn on the living room lights at 80%",
    "What is the energy consumption?",
    "Lock the front door",
    "Trigger the evening scene"
  ],
  "requires": {
    "home_assistant": "2023.1 or newer",
    "python": "3.9+",
    "python_packages": "requests (recommended), urllib3",
    "config": "HOME_ASSISTANT_URL and HOME_ASSISTANT_TOKEN must be set"
  },
  "author": "OpenClaw Community",
  "license": "MIT",
  "repository": "https://github.com/nj070574-gif/openclaw-homeassistant-skill"
}

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)