commit bdfce0a24a466634e4b3f1680713012f6401bd9a Author: zlei9 Date: Sun Mar 29 09:46:25 2026 +0800 Initial commit with translated description diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..6cfee09 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,175 @@ +--- +name: home-assistant +description: "控制Home Assistant智能家居设备、运行自动化和接收Webhook事件。" +metadata: {"clawdbot":{"emoji":"🏠","requires":{"bins":["jq","curl"]}}} +--- + +# Home Assistant + +Control your smart home via Home Assistant's REST API and webhooks. + +## Setup + +### Option 1: Config File (Recommended) + +Create `~/.config/home-assistant/config.json`: +```json +{ + "url": "https://your-ha-instance.duckdns.org", + "token": "your-long-lived-access-token" +} +``` + +### Option 2: Environment Variables + +```bash +export HA_URL="http://homeassistant.local:8123" +export HA_TOKEN="your-long-lived-access-token" +``` + +### Getting a Long-Lived Access Token + +1. Open Home Assistant → Profile (bottom left) +2. Scroll to "Long-Lived Access Tokens" +3. Click "Create Token", name it (e.g., "Clawdbot") +4. Copy the token immediately (shown only once) + +## Quick Reference + +### List Entities + +```bash +curl -s -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/states" | jq '.[].entity_id' +``` + +### Get Entity State + +```bash +curl -s -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/states/light.living_room" +``` + +### Control Devices + +```bash +# Turn on +curl -X POST -H "Authorization: Bearer $HA_TOKEN" -H "Content-Type: application/json" \ + "$HA_URL/api/services/light/turn_on" -d '{"entity_id": "light.living_room"}' + +# Turn off +curl -X POST -H "Authorization: Bearer $HA_TOKEN" -H "Content-Type: application/json" \ + "$HA_URL/api/services/light/turn_off" -d '{"entity_id": "light.living_room"}' + +# Set brightness (0-255) +curl -X POST -H "Authorization: Bearer $HA_TOKEN" -H "Content-Type: application/json" \ + "$HA_URL/api/services/light/turn_on" -d '{"entity_id": "light.living_room", "brightness": 128}' +``` + +### Run Scripts & Automations + +```bash +# Trigger script +curl -X POST -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/services/script/turn_on" \ + -H "Content-Type: application/json" -d '{"entity_id": "script.goodnight"}' + +# Trigger automation +curl -X POST -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/services/automation/trigger" \ + -H "Content-Type: application/json" -d '{"entity_id": "automation.motion_lights"}' +``` + +### Activate Scenes + +```bash +curl -X POST -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/services/scene/turn_on" \ + -H "Content-Type: application/json" -d '{"entity_id": "scene.movie_night"}' +``` + +## Common Services + +| Domain | Service | Example entity_id | +|--------|---------|-------------------| +| `light` | `turn_on`, `turn_off`, `toggle` | `light.kitchen` | +| `switch` | `turn_on`, `turn_off`, `toggle` | `switch.fan` | +| `climate` | `set_temperature`, `set_hvac_mode` | `climate.thermostat` | +| `cover` | `open_cover`, `close_cover`, `stop_cover` | `cover.garage` | +| `media_player` | `play_media`, `media_pause`, `volume_set` | `media_player.tv` | +| `scene` | `turn_on` | `scene.relax` | +| `script` | `turn_on` | `script.welcome_home` | +| `automation` | `trigger`, `turn_on`, `turn_off` | `automation.sunrise` | + +## Inbound Webhooks (HA → Clawdbot) + +To receive events from Home Assistant automations: + +### 1. Create HA Automation with Webhook Action + +```yaml +# In HA automation +action: + - service: rest_command.notify_clawdbot + data: + event: motion_detected + area: living_room +``` + +### 2. Define REST Command in HA + +```yaml +# configuration.yaml +rest_command: + notify_clawdbot: + url: "https://your-clawdbot-url/webhook/home-assistant" + method: POST + headers: + Authorization: "Bearer {{ webhook_secret }}" + Content-Type: "application/json" + payload: '{"event": "{{ event }}", "area": "{{ area }}"}' +``` + +### 3. Handle in Clawdbot + +Clawdbot receives the webhook and can notify you or take action based on the event. + +## CLI Wrapper + +The `scripts/ha.sh` CLI provides easy access to all HA functions: + +```bash +# Test connection +ha.sh info + +# List entities +ha.sh list all # all entities +ha.sh list lights # just lights +ha.sh list switch # just switches + +# Search entities +ha.sh search kitchen # find entities by name + +# Get/set state +ha.sh state light.living_room +ha.sh states light.living_room # full details with attributes +ha.sh on light.living_room +ha.sh on light.living_room 200 # with brightness (0-255) +ha.sh off light.living_room +ha.sh toggle switch.fan + +# Scenes & scripts +ha.sh scene movie_night +ha.sh script goodnight + +# Climate +ha.sh climate climate.thermostat 22 + +# Call any service +ha.sh call light turn_on '{"entity_id":"light.room","brightness":200}' +``` + +## Troubleshooting + +- **401 Unauthorized**: Token expired or invalid. Generate a new one. +- **Connection refused**: Check HA_URL, ensure HA is running and accessible. +- **Entity not found**: List entities to find the correct entity_id. + +## API Reference + +For advanced usage, see [references/api.md](references/api.md). diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..4fcbdbb --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn739j7n05ptqcedg52zgnhrfh7zx24g", + "slug": "home-assistant", + "version": "1.0.0", + "publishedAt": 1769638965135 +} \ No newline at end of file diff --git a/references/api.md b/references/api.md new file mode 100644 index 0000000..baf6b71 --- /dev/null +++ b/references/api.md @@ -0,0 +1,175 @@ +# Home Assistant REST API Reference + +## Authentication + +All requests require the `Authorization` header: + +``` +Authorization: Bearer +``` + +## Base Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/` | GET | API status and HA version | +| `/api/config` | GET | Current configuration | +| `/api/states` | GET | All entity states | +| `/api/states/` | GET | Single entity state | +| `/api/states/` | POST | Set entity state | +| `/api/services` | GET | Available services | +| `/api/services//` | POST | Call a service | +| `/api/events` | GET | Available events | +| `/api/events/` | POST | Fire an event | +| `/api/history/period/` | GET | State history | +| `/api/logbook/` | GET | Logbook entries | + +## Common Services + +### Lights + +```bash +# Turn on with options +POST /api/services/light/turn_on +{ + "entity_id": "light.living_room", + "brightness": 255, # 0-255 + "color_temp": 370, # Mireds (153-500 typically) + "rgb_color": [255, 0, 0], # RGB array + "transition": 2 # Seconds +} + +# Turn off +POST /api/services/light/turn_off +{"entity_id": "light.living_room"} +``` + +### Climate + +```bash +# Set temperature +POST /api/services/climate/set_temperature +{ + "entity_id": "climate.thermostat", + "temperature": 22, + "hvac_mode": "heat" # heat, cool, auto, off +} + +# Set preset +POST /api/services/climate/set_preset_mode +{ + "entity_id": "climate.thermostat", + "preset_mode": "away" +} +``` + +### Media Player + +```bash +# Play/pause +POST /api/services/media_player/media_play_pause +{"entity_id": "media_player.tv"} + +# Set volume (0.0-1.0) +POST /api/services/media_player/volume_set +{"entity_id": "media_player.tv", "volume_level": 0.5} + +# Play media +POST /api/services/media_player/play_media +{ + "entity_id": "media_player.tv", + "media_content_type": "music", + "media_content_id": "spotify:playlist:xyz" +} +``` + +### Cover (Blinds/Garage) + +```bash +POST /api/services/cover/open_cover +{"entity_id": "cover.garage"} + +POST /api/services/cover/close_cover +{"entity_id": "cover.garage"} + +POST /api/services/cover/set_cover_position +{"entity_id": "cover.blinds", "position": 50} # 0=closed, 100=open +``` + +### Notifications + +```bash +POST /api/services/notify/mobile_app_phone +{ + "message": "Motion detected!", + "title": "Security Alert", + "data": { + "image": "/local/camera_snapshot.jpg" + } +} +``` + +## Entity State Object + +```json +{ + "entity_id": "light.living_room", + "state": "on", + "attributes": { + "brightness": 255, + "color_temp": 370, + "friendly_name": "Living Room Light", + "supported_features": 63 + }, + "last_changed": "2024-01-15T10:30:00+00:00", + "last_updated": "2024-01-15T10:30:00+00:00" +} +``` + +## Webhooks (Inbound to HA) + +Trigger automations via webhook: + +```bash +POST /api/webhook/ +{"custom": "data"} +``` + +Create webhook trigger in automation: + +```yaml +automation: + trigger: + - platform: webhook + webhook_id: my_webhook_id + allowed_methods: + - POST +``` + +## WebSocket API + +For real-time updates, use the WebSocket API at `ws://ha-url/api/websocket`. + +Connection flow: +1. Connect to WebSocket +2. Receive `auth_required` +3. Send `{"type": "auth", "access_token": "TOKEN"}` +4. Receive `auth_ok` +5. Subscribe to events: `{"id": 1, "type": "subscribe_events", "event_type": "state_changed"}` + +## Error Responses + +| Code | Meaning | +|------|---------| +| 400 | Bad request (invalid JSON or missing fields) | +| 401 | Unauthorized (invalid/missing token) | +| 404 | Entity or service not found | +| 405 | Method not allowed | + +## Rate Limits + +Home Assistant doesn't enforce strict rate limits, but avoid: +- Polling faster than every 1 second +- Bulk updates without batching + +Use WebSocket for real-time state tracking instead of polling. diff --git a/scripts/ha.sh b/scripts/ha.sh new file mode 100644 index 0000000..2ba06c9 --- /dev/null +++ b/scripts/ha.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +# Home Assistant CLI wrapper +# Usage: ha.sh [args...] + +set -euo pipefail + +CONFIG_FILE="${HA_CONFIG:-$HOME/.config/home-assistant/config.json}" + +# Load config +if [[ -f "$CONFIG_FILE" ]]; then + HA_URL="${HA_URL:-$(jq -r '.url // empty' "$CONFIG_FILE")}" + HA_TOKEN="${HA_TOKEN:-$(jq -r '.token // empty' "$CONFIG_FILE")}" +fi + +: "${HA_URL:?Set HA_URL or configure $CONFIG_FILE}" +: "${HA_TOKEN:?Set HA_TOKEN or configure $CONFIG_FILE}" + +cmd="${1:-help}" +shift || true + +api() { + curl -s -H "Authorization: Bearer $HA_TOKEN" -H "Content-Type: application/json" "$@" +} + +case "$cmd" in + state|get) + # Get entity state: ha.sh state light.living_room + entity="${1:?Usage: ha.sh state }" + api "$HA_URL/api/states/$entity" | jq -r '.state // "unknown"' + ;; + + states) + # Get full entity state with attributes + entity="${1:?Usage: ha.sh states }" + api "$HA_URL/api/states/$entity" | jq + ;; + + on|turn_on) + # Turn on entity: ha.sh on light.living_room [brightness] + entity="${1:?Usage: ha.sh on [brightness]}" + domain="${entity%%.*}" + brightness="${2:-}" + if [[ -n "$brightness" ]]; then + api -X POST "$HA_URL/api/services/$domain/turn_on" \ + -d "{\"entity_id\": \"$entity\", \"brightness\": $brightness}" + else + api -X POST "$HA_URL/api/services/$domain/turn_on" \ + -d "{\"entity_id\": \"$entity\"}" + fi + echo "✓ $entity turned on" + ;; + + off|turn_off) + # Turn off entity: ha.sh off light.living_room + entity="${1:?Usage: ha.sh off }" + domain="${entity%%.*}" + api -X POST "$HA_URL/api/services/$domain/turn_off" \ + -d "{\"entity_id\": \"$entity\"}" >/dev/null + echo "✓ $entity turned off" + ;; + + toggle) + # Toggle entity: ha.sh toggle switch.fan + entity="${1:?Usage: ha.sh toggle }" + domain="${entity%%.*}" + api -X POST "$HA_URL/api/services/$domain/toggle" \ + -d "{\"entity_id\": \"$entity\"}" >/dev/null + echo "✓ $entity toggled" + ;; + + scene) + # Activate scene: ha.sh scene movie_night + scene="${1:?Usage: ha.sh scene }" + [[ "$scene" == scene.* ]] || scene="scene.$scene" + api -X POST "$HA_URL/api/services/scene/turn_on" \ + -d "{\"entity_id\": \"$scene\"}" >/dev/null + echo "✓ Scene $scene activated" + ;; + + script) + # Run script: ha.sh script goodnight + script="${1:?Usage: ha.sh script }" + [[ "$script" == script.* ]] || script="script.$script" + api -X POST "$HA_URL/api/services/script/turn_on" \ + -d "{\"entity_id\": \"$script\"}" >/dev/null + echo "✓ Script $script executed" + ;; + + automation|trigger) + # Trigger automation: ha.sh automation motion_lights + auto="${1:?Usage: ha.sh automation }" + [[ "$auto" == automation.* ]] || auto="automation.$auto" + api -X POST "$HA_URL/api/services/automation/trigger" \ + -d "{\"entity_id\": \"$auto\"}" >/dev/null + echo "✓ Automation $auto triggered" + ;; + + climate|temp) + # Set temperature: ha.sh climate climate.thermostat 22 + entity="${1:?Usage: ha.sh climate }" + temp="${2:?Usage: ha.sh climate }" + api -X POST "$HA_URL/api/services/climate/set_temperature" \ + -d "{\"entity_id\": \"$entity\", \"temperature\": $temp}" >/dev/null + echo "✓ $entity set to ${temp}°" + ;; + + list) + # List entities by domain: ha.sh list lights / ha.sh list all + filter="${1:-all}" + if [[ "$filter" == "all" ]]; then + api "$HA_URL/api/states" | jq -r '.[].entity_id' | sort + else + # Normalize: "lights" -> "light", "switches" -> "switch" + filter="${filter%s}" + api "$HA_URL/api/states" | jq -r --arg d "$filter" \ + '.[] | select(.entity_id | startswith($d + ".")) | .entity_id' | sort + fi + ;; + + search) + # Search entities: ha.sh search kitchen + pattern="${1:?Usage: ha.sh search }" + api "$HA_URL/api/states" | jq -r --arg p "$pattern" \ + '.[] | select(.entity_id | test($p; "i")) | "\(.entity_id): \(.state)"' + ;; + + call) + # Call any service: ha.sh call light turn_on '{"entity_id":"light.room","brightness":200}' + domain="${1:?Usage: ha.sh call [json_data]}" + service="${2:?Usage: ha.sh call [json_data]}" + data="${3:-{}}" + api -X POST "$HA_URL/api/services/$domain/$service" -d "$data" + ;; + + info) + # Get HA instance info + api "$HA_URL/api/" | jq + ;; + + help|*) + cat < [args...] + +Commands: + state Get entity state + states Get full entity state with attributes + on [brightness] Turn on (optional brightness 0-255) + off Turn off + toggle Toggle on/off + scene Activate scene + script Run script + automation Trigger automation + climate Set temperature + list [domain] List entities (lights, switches, all) + search Search entities by name + call [json] Call any service + info Get HA instance info + +Environment: + HA_URL Home Assistant URL (required) + HA_TOKEN Long-lived access token (required) + +Examples: + ha.sh on light.living_room 200 + ha.sh scene movie_night + ha.sh list lights + ha.sh search kitchen +EOF + ;; +esac