mirror of
https://github.com/hanzoskill/bot-homebridge.git
synced 2026-10-30 21:01:04 +00:00
214 lines
7.0 KiB
Python
214 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Homebridge API client for controlling smart home devices.
|
|
|
|
Usage:
|
|
homebridge_api.py list [--room ROOM] [--type TYPE]
|
|
homebridge_api.py get <accessory_id>
|
|
homebridge_api.py set <accessory_id> <characteristic> <value>
|
|
homebridge_api.py rooms
|
|
|
|
Reads credentials from ~/.bot/bot.json under skills.entries.homebridge:
|
|
url: Homebridge Config UI X URL
|
|
username: Admin username
|
|
password: Admin password
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from urllib.request import Request, urlopen
|
|
from urllib.error import HTTPError, URLError
|
|
|
|
|
|
def load_config() -> dict:
|
|
"""Load Homebridge config from credentials file."""
|
|
config_path = Path.home() / ".bot" / "credentials" / "homebridge.json"
|
|
|
|
if not config_path.exists():
|
|
print(f"Error: Config file not found at {config_path}", file=sys.stderr)
|
|
print("Create it with: {\"url\": \"...\", \"username\": \"...\", \"password\": \"...\"}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
with open(config_path) as f:
|
|
config = json.load(f)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error: Invalid JSON in config file: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
for key in ["url", "username", "password"]:
|
|
if not config.get(key):
|
|
print(f"Error: Missing '{key}' in homebridge.json", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
return config
|
|
|
|
|
|
def make_request(url: str, method: str = "GET", data: dict = None, token: str = None) -> dict:
|
|
"""Make HTTP request to Homebridge API."""
|
|
headers = {"Content-Type": "application/json"}
|
|
if token:
|
|
headers["Authorization"] = f"Bearer {token}"
|
|
|
|
body = json.dumps(data).encode() if data else None
|
|
req = Request(url, data=body, headers=headers, method=method)
|
|
|
|
try:
|
|
with urlopen(req, timeout=30) as resp:
|
|
return json.loads(resp.read().decode())
|
|
except HTTPError as e:
|
|
error_body = e.read().decode() if e.fp else ""
|
|
print(f"HTTP Error {e.code}: {e.reason}", file=sys.stderr)
|
|
if error_body:
|
|
print(f"Response: {error_body}", file=sys.stderr)
|
|
sys.exit(1)
|
|
except URLError as e:
|
|
print(f"Connection error: {e.reason}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def authenticate(base_url: str, username: str, password: str) -> str:
|
|
"""Authenticate and return access token."""
|
|
url = f"{base_url}/api/auth/login"
|
|
data = {"username": username, "password": password}
|
|
response = make_request(url, method="POST", data=data)
|
|
return response.get("access_token")
|
|
|
|
|
|
def list_accessories(base_url: str, token: str, room: str = None, acc_type: str = None) -> list:
|
|
"""List all accessories, optionally filtered by room or type."""
|
|
url = f"{base_url}/api/accessories"
|
|
accessories = make_request(url, token=token)
|
|
|
|
results = []
|
|
for acc in accessories:
|
|
# Filter by type if specified
|
|
if acc_type and acc.get("type") != acc_type:
|
|
continue
|
|
|
|
results.append({
|
|
"uniqueId": acc.get("uniqueId"),
|
|
"serviceName": acc.get("serviceName"),
|
|
"type": acc.get("type"),
|
|
"values": acc.get("values", {}),
|
|
})
|
|
|
|
# If room filter is specified, get layout and filter
|
|
if room:
|
|
layout_url = f"{base_url}/api/accessories/layout"
|
|
layout = make_request(layout_url, token=token)
|
|
|
|
room_ids = set()
|
|
for r in layout:
|
|
if room.lower() in r.get("name", "").lower():
|
|
for service in r.get("services", []):
|
|
room_ids.add(service.get("uniqueId"))
|
|
|
|
results = [acc for acc in results if acc["uniqueId"] in room_ids]
|
|
|
|
return results
|
|
|
|
|
|
def get_accessory(base_url: str, token: str, accessory_id: str) -> dict:
|
|
"""Get details of a specific accessory."""
|
|
url = f"{base_url}/api/accessories/{accessory_id}"
|
|
return make_request(url, token=token)
|
|
|
|
|
|
def set_characteristic(base_url: str, token: str, accessory_id: str,
|
|
characteristic: str, value) -> dict:
|
|
"""Set a characteristic value on an accessory."""
|
|
url = f"{base_url}/api/accessories/{accessory_id}"
|
|
|
|
# Convert value to appropriate type
|
|
if isinstance(value, str):
|
|
if value.lower() == "true":
|
|
value = True
|
|
elif value.lower() == "false":
|
|
value = False
|
|
elif value.isdigit():
|
|
value = int(value)
|
|
else:
|
|
try:
|
|
value = float(value)
|
|
except ValueError:
|
|
pass
|
|
|
|
data = {
|
|
"characteristicType": characteristic,
|
|
"value": value
|
|
}
|
|
return make_request(url, method="PUT", data=data, token=token)
|
|
|
|
|
|
def get_rooms(base_url: str, token: str) -> list:
|
|
"""Get room layout with accessories."""
|
|
url = f"{base_url}/api/accessories/layout"
|
|
layout = make_request(url, token=token)
|
|
|
|
rooms = []
|
|
for room in layout:
|
|
rooms.append({
|
|
"name": room.get("name"),
|
|
"accessories": [s.get("serviceName") for s in room.get("services", [])]
|
|
})
|
|
return rooms
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Homebridge API client")
|
|
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
|
|
# List command
|
|
list_parser = subparsers.add_parser("list", help="List accessories")
|
|
list_parser.add_argument("--room", help="Filter by room name")
|
|
list_parser.add_argument("--type", dest="acc_type", help="Filter by accessory type")
|
|
|
|
# Get command
|
|
get_parser = subparsers.add_parser("get", help="Get accessory details")
|
|
get_parser.add_argument("accessory_id", help="Accessory unique ID")
|
|
|
|
# Set command
|
|
set_parser = subparsers.add_parser("set", help="Set accessory characteristic")
|
|
set_parser.add_argument("accessory_id", help="Accessory unique ID")
|
|
set_parser.add_argument("characteristic", help="Characteristic type (e.g., On, Brightness)")
|
|
set_parser.add_argument("value", help="Value to set")
|
|
|
|
# Rooms command
|
|
subparsers.add_parser("rooms", help="List rooms and their accessories")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
# Load credentials from config
|
|
config = load_config()
|
|
base_url = config["url"].rstrip("/")
|
|
username = config["username"]
|
|
password = config["password"]
|
|
|
|
# Authenticate
|
|
token = authenticate(base_url, username, password)
|
|
|
|
# Execute command
|
|
if args.command == "list":
|
|
result = list_accessories(base_url, token, args.room, args.acc_type)
|
|
elif args.command == "get":
|
|
result = get_accessory(base_url, token, args.accessory_id)
|
|
elif args.command == "set":
|
|
result = set_characteristic(base_url, token, args.accessory_id,
|
|
args.characteristic, args.value)
|
|
elif args.command == "rooms":
|
|
result = get_rooms(base_url, token)
|
|
|
|
print(json.dumps(result, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|