Initial commit: Javis project

This commit is contained in:
Javis committed 2026-09-21 21:44:00 +02:00
commit 435c645f36
164 files changed
+35279

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
# Homey connection
HOMEY_HOST=192.168.178.201
HOMEY_TOKEN=4a6d5464-2e25-4fbe-bd2a-f9c4902c90e5:ac38c7fb-3131-4af5-997e-fd0e22c931c3:a3445d0d939932384f5de2594dae1857f5d2a53f
HOMEY_SCHEME=http
HOMEY_PORT=4859
HOMEY_TIMEOUT=10
HOMEY_EVENT_BUFFER=200
HOMEY_REALTIME=true
# LAN transport (optional) - only used by run-lan.bat / HOMEY_MCP_TRANSPORT=streamable-http
# HOMEY_MCP_TRANSPORT=streamable-http
# HOMEY_MCP_HOST=0.0.0.0 # uvicorn bind address (0.0.0.0 for all interfaces)
# HOMEY_MCP_ADVERTISED_HOST=10.10.10.50 # advertised host for MCP Host header validation
# HOMEY_MCP_PORT=8080
# HOMEY_MCP_AUTH_MODE=none
# HOMEY_MCP_AUTH_TOKEN=replace-with-a-long-random-secret
+22
View File
@@ -0,0 +1,22 @@
# Python
__pycache__/
*.py[cod]
*.egg-info/
.eggs/
build/
dist/
.venv/
venv/
# Env
.env
.env.local
# Tooling
.mypy_cache/
.pytest_cache/
.ruff_cache/
# IDE
.vscode/
.idea/
+200
View File
@@ -0,0 +1,200 @@
# homey-mcp
An MCP (Model Context Protocol) server that exposes a local **Homey Pro** instance to LLM clients (Claude Desktop, VS Code / GitHub Copilot Chat, MCP Inspector, …).
It talks to Homey's local HTTP Web API (`/api/manager/...`) using a Personal Access Token, and optionally subscribes to Socket.IO realtime events.
## Features
Tools:
- `ping` — verify connectivity / return Homey system info
- `list_devices(zone_id?)` — devices with current capability values
- `get_device(device_id)` — full device object
- `list_zones` — rooms / areas
- `set_capability(device_id, capability, value)` — set `onoff`, `dim`, `target_temperature`, …
- `list_flows` — all flows
- `trigger_flow(flow_id)` — start a flow
- `list_recent_events(limit, kind?)` — recent realtime events from the in-memory buffer
Resources:
- `homey://devices`
- `homey://zones`
- `homey://events/recent`
Transport: **stdio** (default, for local LLM hosts like Javis) or **streamable-http / sse** (for remote MCP clients over the LAN, e.g. the Odysseus AI helper) — see [Run on the LAN](#run-on-the-lan-remote-mcp-clients) below.
## Prerequisites
- Python **3.10+**
- A Homey Pro on the local network
- A **Personal Access Token** for the Homey Web API — create one at the Homey Developer Tools: <https://tools.developer.homey.app/> (My Homey → API Keys / Personal Access Token).
## Install
From this folder:
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e .
```
`uv` works equivalently:
```powershell
uv venv
uv pip install -e .
```
## Configure
Copy `.env.example` to `.env` and fill in:
```ini
HOMEY_HOST=192.168.1.10
HOMEY_TOKEN=eyJhbGciOi...your PAT...
HOMEY_SCHEME=http
HOMEY_PORT=4859
HOMEY_TIMEOUT=10
HOMEY_EVENT_BUFFER=200
HOMEY_REALTIME=true
```
## Run
```powershell
homey-mcp
# or
python -m homey_mcp
# or
.\run.bat
```
### Inspect with the MCP Inspector
```powershell
uv run mcp dev src/homey_mcp/server.py
# or
npx -y @modelcontextprotocol/inspector homey-mcp
```
### Run on the LAN (remote MCP clients)
To let a remote MCP client on the same network (e.g. **Odysseus**) connect over
HTTP instead of stdio, set the transport env vars and run `run-lan.bat`
(or set them yourself and run `homey-mcp` / `python -m homey_mcp`):
```ini
HOMEY_MCP_TRANSPORT=streamable-http # or: sse
HOMEY_MCP_HOST=0.0.0.0 # bind address (or specific LAN IP)
HOMEY_MCP_ADVERTISED_HOST=10.10.10.50 # advertised host for MCP validation (required for 0.0.0.0 bind)
HOMEY_MCP_PORT=8080
HOMEY_MCP_AUTH_MODE=none # compatible trusted-LAN default
HOMEY_MCP_AUTH_TOKEN=<a long random secret>
```
```powershell
.\run-lan.bat
```
The remote client connects to `http://<this-machine-ip>:8080/mcp`
(streamable-http) or `/sse`. Streamable HTTP clients that only accept a base
URL may use `http://<this-machine-ip>:8080/`; MCP traffic at `/` is forwarded
to `/mcp`, while an ordinary browser GET returns service discovery.
**Important for MCP transport over LAN:** The MCP SDK validates that clients
send the correct `Host` header. If you bind to `0.0.0.0`, you **must** set
`HOMEY_MCP_ADVERTISED_HOST` to your actual server IP (e.g., `10.10.10.50`).
If you instead bind to a specific IP, omit `HOMEY_MCP_ADVERTISED_HOST` and it
will default to the bind address.
`HOMEY_MCP_AUTH_MODE=none` is the compatible mode for OAuth-negotiating MCP
clients such as Odysseus, because this server is not an OAuth authorization
server. Use it only on a trusted, firewall-restricted LAN. Set the mode to
`bearer` only when the MCP client can be configured to send
`Authorization: Bearer <HOMEY_MCP_AUTH_TOKEN>` directly; OAuth discovery and
dynamic registration are not provided. Generate a token with
`python -c "import secrets; print(secrets.token_urlsafe(32))"`.
When `HOMEY_MCP_AUTH_TOKEN` is set, the OpenAPI REST routes always require it,
regardless of the MCP auth mode.
This is independent of Javis's own connection: Javis keeps talking to
homey-mcp over stdio via `mcp_servers.json` as before — run the LAN instance
as a second, separate process for other clients.
#### Odysseus API integration
If Odysseus exposes integrations through its `app_api` tool rather than a
native MCP client, import this server's OpenAPI document:
- Schema URL: `http://<this-machine-ip>:8080/openapi.json`
- Authentication: Bearer token
- Token: the value of `HOMEY_MCP_AUTH_TOKEN`
The API provides reads at `/api/system`, `/api/zones`, and `/api/devices`,
plus writes at `/api/devices/{device_id}/capabilities/{capability}` and
`/api/zones/control`.
Odysseus must invoke its registered `app_api` tool as a structured tool call.
It must not print or execute Python such as `api_call(...)`; `api_call` is not
a Python function and is not the registered tool name. Use this integration
instruction in Odysseus:
```text
For Homey requests, always call the app_api tool with integration="Homey".
Never write api_call(...) or place tool calls in Python/code blocks.
Use GET /api/system to check connectivity, GET /api/zones and GET /api/devices
to discover IDs, PUT /api/devices/{device_id}/capabilities/{capability} with
{"value": ...} for one device, and POST /api/zones/control with
{"zone": "...", "capability": "onoff", "value": true|false} for a room.
After the tool returns, summarize the actual result. Never claim success before
receiving a successful tool response.
```
### Use from Claude Desktop / VS Code
Add to your MCP client config (path varies per client):
```json
{
"mcpServers": {
"homey": {
"command": "homey-mcp",
"env": {
"HOMEY_HOST": "192.168.1.10",
"HOMEY_TOKEN": "your-personal-access-token"
}
}
}
}
```
If `homey-mcp` is not on PATH, use the full venv path, e.g. `C:\\local\\Code\\PRIVAT\\homey-mcp\\.venv\\Scripts\\homey-mcp.exe`.
## Notes & limitations
- Local API only (no cloud OAuth).
- LAN MCP transport authentication is controlled by `HOMEY_MCP_AUTH_MODE`.
Never expose `none` mode beyond a trusted, firewall-restricted LAN.
- Realtime events are best-effort — if the Socket.IO connection fails the REST tools still work. Set `HOMEY_REALTIME=false` to disable.
- No admin operations (zone/user/app management) are exposed.
- Capability values are typed as `bool | int | float | str`; the model picks based on the capability (`onoff` → bool, `dim` → 0..1 float, etc.).
## Project layout
```
homey-mcp/
pyproject.toml
README.md
.env.example
run.bat
src/homey_mcp/
__init__.py
__main__.py
config.py
homey_client.py
server.py
```
+137
View File
@@ -0,0 +1,137 @@
# Homey MCP Server - Ubuntu Setup Guide
## Quick Start
### 1. Run the setup script
```bash
cd homey-mcp
chmod +x setup-ubuntu.sh run.sh run-lan.sh
./setup-ubuntu.sh
```
### 2. Configure your credentials
Edit the `.env` file with your Homey Pro credentials:
```bash
nano .env
```
Required fields:
- `HOMEY_TOKEN` - Your Homey Pro personal access token
- `HOMEY_HOST` - Your Homey Pro hub IP (e.g., `192.168.1.100`)
- `HOMEY_SCHEME` / `HOMEY_PORT` - Local API protocol and port
Optional for LAN mode:
- `HOMEY_MCP_AUTH_MODE` - `none` for trusted-LAN compatibility, or `bearer`
- `HOMEY_MCP_AUTH_TOKEN` - Protects REST and, in `bearer` mode, MCP requests
- `HOMEY_MCP_ADVERTISED_HOST` - Set this to your server's LAN IP (e.g., `10.10.10.50`). Required if using `HOMEY_MCP_HOST=0.0.0.0`.
### 3. Start the server
**For remote Odysseus client (LAN mode):**
```bash
./run-lan.sh
```
**For local stdio mode (Javis integration):**
```bash
./run.sh
```
## Manual Installation
If you prefer to set up manually:
```bash
# Install Python (if not installed)
sudo apt install python3.10 python3.venv build-essential libffi-dev libssl-dev
# Create virtual environment
cd homey-mcp
python3 -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install --upgrade pip
pip install -e .
pip install uvicorn
# Configure
cp .env.example .env
nano .env # Edit with your credentials
# Run
HOMEY_MCP_TRANSPORT=streamable-http HOMEY_MCP_HOST=0.0.0.0 HOMEY_MCP_PORT=8080 \
python -m homey_mcp
```
## Persistent Operation with systemd
Install the service file for automatic startup:
```bash
# Copy service file
sudo cp homey-mcp.service /etc/systemd/system/
# Reload systemd
sudo systemctl daemon-reload
# Enable and start (replace $USER with your username)
sudo systemctl enable --now homey-mcp@$USER.service
# Check status
sudo journalctl -u homey-mcp@$USER.service -f
```
## Connecting Odysseus
To connect Odysseus as an MCP client, use the streamable-http transport:
1. **Important:** Before starting the server, set `HOMEY_MCP_ADVERTISED_HOST` in `.env` to your Ubuntu server's LAN IP address (e.g., `10.10.10.50`). This is required for MCP Host header validation.
2. Ensure homey-mcp is running on port 8080 (or your configured port)
3. Configure Odysseus with the MCP endpoint:
```
URL: http://<YOUR_UBUNTU_IP>:8080/mcp
Transport: streamable-http
Authentication: none
```
If you see a "421 Misdirected Request" error, your `HOMEY_MCP_ADVERTISED_HOST` is not set correctly; it must match the IP that Odysseus uses to connect.
Use `HOMEY_MCP_AUTH_MODE=none` only on a trusted, firewall-restricted LAN.
This avoids an OAuth discovery flow, which homey-mcp does not implement. If
Odysseus supports a manually configured static Authorization header, you may
instead set `HOMEY_MCP_AUTH_MODE=bearer` and configure the value of
`HOMEY_MCP_AUTH_TOKEN` as its bearer token.
For Odysseus OpenAPI/`app_api` integration, import
`http://<YOUR_UBUNTU_IP>:8080/openapi.json` and configure
`HOMEY_MCP_AUTH_TOKEN` as a bearer token. REST routes remain protected even
when MCP authentication is disabled.
## Troubleshooting
### Virtual environment issues
```bash
rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```
### Port already in use
```bash
# Check what's using the port
sudo lsof -i :8080
# Use a different port
HOMEY_MCP_PORT=8081 ./run-lan.sh
```
### Permission denied
```bash
chmod +x run.sh run-lan.sh setup-ubuntu.sh
```
### Check logs
```bash
sudo journalctl -u homey-mcp@$USER.service -f --since "1 hour ago"
+27
View File
@@ -0,0 +1,27 @@
[Unit]
Description=Homey MCP Server (LAN mode for Odysseus)
After=network.target
Wants=network.target
[Service]
Type=simple
User=%i
WorkingDirectory=%h/homey-mcp
ExecStart=%h/homey-mcp/run-lan.sh
Environment=HOMEY_MCP_TRANSPORT=streamable-http
Environment=HOMEY_MCP_HOST=0.0.0.0
Environment=HOMEY_MCP_PORT=8080
EnvironmentFile=%h/homey-mcp/.env
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=%h/homey-mcp
ProtectHome=read-only
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
{
"mcpServers": {
"homey": {
"url": "http://<HOMEY_IP>:8080/mcp",
"headers": {
"Authorization": "Bearer <HOMEY_MCP_AUTH_TOKEN>"
},
"transport": "streamable-http"
}
}
}
+40
View File
@@ -0,0 +1,40 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "homey-mcp"
version = "0.1.0"
description = "MCP server exposing a local Homey Pro instance to LLM clients."
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
authors = [{ name = "homey-mcp" }]
keywords = ["mcp", "homey", "llm", "smart-home"]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"mcp[cli]>=1.2",
"httpx>=0.27",
"python-socketio[asyncio_client]>=5.11",
"pydantic>=2.7",
"pydantic-settings>=2.3",
"python-dotenv>=1.0",
"uvicorn[standard]>=0.29",
"starlette>=0.37",
]
[project.scripts]
homey-mcp = "homey_mcp.server:main"
[project.urls]
Homepage = "https://api.developer.homey.app/"
[tool.hatch.build.targets.wheel]
packages = ["src/homey_mcp"]
+22
View File
@@ -0,0 +1,22 @@
@echo off
rem Runs homey-mcp as a LAN-reachable HTTP service (streamable-http transport)
rem for remote MCP clients (e.g. the Odysseus AI helper), instead of the
rem stdio transport Javis uses locally.
setlocal
cd /d "%~dp0"
if not exist ".venv\Scripts\python.exe" (
echo Creating virtual environment...
python -m venv .venv
call .venv\Scripts\activate.bat
python -m pip install --upgrade pip
pip install -e .
) else (
call .venv\Scripts\activate.bat
)
set HOMEY_MCP_TRANSPORT=streamable-http
if "%HOMEY_MCP_HOST%"=="" set HOMEY_MCP_HOST=0.0.0.0
if "%HOMEY_MCP_PORT%"=="" set HOMEY_MCP_PORT=8080
python -m homey_mcp %*
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Run homey-mcp as a LAN-reachable HTTP MCP server for remote clients (e.g., Odysseus).
# Usage: ./run-lan.sh
#
# Environment variables (with defaults):
# HOMEY_MCP_TRANSPORT - streamable-http (default) or sse
# HOMEY_MCP_HOST - 0.0.0.0 (default)
# HOMEY_MCP_PORT - 8080 (default)
# HOMEY_MCP_AUTH_MODE - none (compatible default) or bearer
# HOMEY_MCP_AUTH_TOKEN - protects REST; also MCP when auth mode is bearer
set -euo pipefail
cd "$(dirname "$0")"
# Create virtual environment if it doesn't exist
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
python3 -m venv .venv
fi
source .venv/bin/activate
# Upgrade pip and install dependencies
python -m pip install --upgrade pip >/dev/null 2>&1
pip install -e . >/dev/null 2>&1
# Set transport to streamable-http (recommended) or sse
export HOMEY_MCP_TRANSPORT="${HOMEY_MCP_TRANSPORT:-streamable-http}"
export HOMEY_MCP_HOST="${HOMEY_MCP_HOST:-0.0.0.0}"
export HOMEY_MCP_PORT="${HOMEY_MCP_PORT:-8080}"
echo "Starting homey-mcp (LAN mode) on http://${HOMEY_MCP_HOST}:${HOMEY_MCP_PORT}"
echo "Transport: ${HOMEY_MCP_TRANSPORT}"
exec python -m homey_mcp "$@"
+15
View File
@@ -0,0 +1,15 @@
@echo off
setlocal
cd /d "%~dp0"
if not exist ".venv\Scripts\python.exe" (
echo Creating virtual environment...
python -m venv .venv
call .venv\Scripts\activate.bat
python -m pip install --upgrade pip
pip install -e .
) else (
call .venv\Scripts\activate.bat
)
python -m homey_mcp %*
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Run homey-mcp as a local MCP server via stdio transport.
# Usage: ./run.sh
set -euo pipefail
cd "$(dirname "$0")"
# Create virtual environment if it doesn't exist
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
python3 -m venv .venv
fi
source .venv/bin/activate
# Upgrade pip and install dependencies
python -m pip install --upgrade pip >/dev/null 2>&1
pip install -e . >/dev/null 2>&1
# Run with stdio transport (default for local MCP clients)
export HOMEY_MCP_TRANSPORT="${HOMEY_MCP_TRANSPORT:-stdio}"
exec python -m homey_mcp "$@"
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# One-time setup script for Ubuntu environment.
# Installs system dependencies, sets up the Python venv, and prepares .env.
set -euo pipefail
cd "$(dirname "$0")"
echo "=== Homey MCP Server Ubuntu Setup ==="
# 1. Check Python version
PYTHON_CMD=""
if command -v python3 &>/dev/null; then
PYTHON_CMD="python3"
elif command -v python &>/dev/null; then
PYTHON_CMD="python"
else
echo "ERROR: Python not found. Install Python 3.10+ first."
echo " Ubuntu: sudo apt install python3.10 python3.venv"
exit 1
fi
PY_VER=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
echo "Found Python $PY_VER"
# 2. Install system dependencies if needed
if ! command -v uvicorn &>/dev/null 2>&1; then
echo "Installing system dependencies..."
sudo apt-get update -qq
sudo apt-get install -y python3-pip python3-dev python3-venv build-essential libffi-dev libssl-dev
fi
# 3. Create virtual environment if it doesn't exist
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
$PYTHON_CMD -m venv .venv
fi
source .venv/bin/activate
# 4. Install/update Python dependencies
echo "Installing Python dependencies..."
$PYTHON_CMD -m pip install --upgrade pip >/dev/null 2>&1
pip install -e . >/dev/null 2>&1
pip install uvicorn >/dev/null 2>&1
# 5. Create .env file from example if it doesn't exist
if [ ! -f ".env" ]; then
if [ -f ".env.example" ]; then
echo "Creating .env from .env.example (please edit with your credentials)..."
cp .env.example .env
else
echo "Creating minimal .env file..."
cat > .env << 'EOF'
# Homey Pro API configuration
HOMEY_HOST=your-homey-pro-ip
HOMEY_TOKEN=your_homey_api_token_here
HOMEY_SCHEME=http
HOMEY_PORT=4859
# MCP server configuration (LAN mode)
HOMEY_MCP_TRANSPORT=streamable-http
HOMEY_MCP_HOST=0.0.0.0
HOMEY_MCP_ADVERTISED_HOST=<your-server-ip>
HOMEY_MCP_PORT=8080
HOMEY_MCP_AUTH_MODE=none
HOMEY_MCP_AUTH_TOKEN=your_mcp_auth_token_here
# Socket.IO realtime configuration (optional)
HOMEY_REALTIME=true
EOF
fi
fi
echo ""
echo "=== Setup complete! ==="
echo ""
echo "Next steps:"
echo "1. Edit .env with your Homey Pro credentials (HOMEY_TOKEN and HOMEY_HOST)"
echo "2. For LAN mode (recommended for Odysseus): ./run-lan.sh"
echo "3. For local stdio mode: ./run.sh"
echo ""
echo "For persistent operation, install the systemd service:"
echo " sudo cp homey-mcp.service /etc/systemd/system/"
echo " sudo systemctl daemon-reload"
echo " sudo systemctl enable --now homey-mcp@$(whoami).service"
echo ""
+3
View File
@@ -0,0 +1,3 @@
"""homey-mcp: MCP server for a local Homey Pro instance."""
__version__ = "0.1.0"
+4
View File
@@ -0,0 +1,4 @@
from .server import main
if __name__ == "__main__":
main()
+42
View File
@@ -0,0 +1,42 @@
"""Configuration loaded from environment variables / .env."""
from __future__ import annotations
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Settings for the homey-mcp server.
Values are read from environment variables (prefix ``HOMEY_``) or a
local ``.env`` file.
"""
model_config = SettingsConfigDict(
env_prefix="HOMEY_",
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
host: str = Field(..., description="IP or hostname of the Homey Pro.")
token: str = Field(..., description="Personal Access Token (Bearer).")
scheme: str = Field("http", description="http or https.")
port: int | None = Field(
None,
description="Optional TCP port (e.g. 4859 when Homey runs in a container).",
)
timeout: float = Field(10.0, description="HTTP timeout in seconds.")
event_buffer: int = Field(200, description="Max realtime events to retain.")
realtime: bool = Field(True, description="Enable Socket.IO realtime events.")
@property
def base_url(self) -> str:
if self.port is None:
return f"{self.scheme}://{self.host}"
return f"{self.scheme}://{self.host}:{self.port}"
def load_settings() -> Settings:
return Settings() # type: ignore[call-arg]
+180
View File
@@ -0,0 +1,180 @@
"""Async client for the Homey Pro local Web API + Socket.IO realtime events."""
from __future__ import annotations
import logging
import time
from collections import deque
from typing import Any
import httpx
try:
import socketio # type: ignore
except ImportError: # pragma: no cover
socketio = None # type: ignore
from .config import Settings
log = logging.getLogger(__name__)
class HomeyClient:
"""Thin async wrapper around Homey's local HTTP + Socket.IO APIs."""
def __init__(self, settings: Settings) -> None:
self.settings = settings
self._http = httpx.AsyncClient(
base_url=settings.base_url,
headers={
"Authorization": f"Bearer {settings.token}",
"Accept": "application/json",
},
timeout=settings.timeout,
)
self._events: deque[dict[str, Any]] = deque(maxlen=settings.event_buffer)
self._sio: Any | None = None
self._sio_connected: bool = False
# ---- lifecycle --------------------------------------------------------
async def aclose(self) -> None:
try:
if self._sio is not None and self._sio_connected:
await self._sio.disconnect()
except Exception: # pragma: no cover
log.exception("Error disconnecting Socket.IO")
await self._http.aclose()
# ---- helpers ----------------------------------------------------------
async def _get(self, path: str) -> Any:
r = await self._http.get(path)
r.raise_for_status()
return r.json()
async def _post(self, path: str, json: Any | None = None) -> Any:
r = await self._http.post(path, json=json)
r.raise_for_status()
if r.content:
try:
return r.json()
except ValueError:
return None
return None
async def _put(self, path: str, json: Any | None = None) -> Any:
r = await self._http.put(path, json=json)
r.raise_for_status()
if r.content:
try:
return r.json()
except ValueError:
return None
return None
# ---- REST: devices / zones / flows -----------------------------------
async def get_system_info(self) -> dict[str, Any]:
return await self._get("/api/manager/system/info")
async def list_devices(self) -> dict[str, Any]:
return await self._get("/api/manager/devices/device")
async def get_device(self, device_id: str) -> dict[str, Any]:
return await self._get(f"/api/manager/devices/device/{device_id}")
async def list_zones(self) -> dict[str, Any]:
return await self._get("/api/manager/zones/zone")
async def set_capability(
self, device_id: str, capability_id: str, value: Any
) -> Any:
return await self._put(
f"/api/manager/devices/device/{device_id}/capability/{capability_id}",
json={"value": value},
)
async def list_flows(self) -> dict[str, Any]:
return await self._get("/api/manager/flow/flow")
async def trigger_flow(self, flow_id: str) -> Any:
return await self._post(f"/api/manager/flow/flow/{flow_id}/trigger")
# ---- REST: logic variables -------------------------------------------
async def list_variables(self) -> dict[str, Any]:
"""Return all Homey Logic Variables (type, current value, etc.)."""
return await self._get("/api/manager/logic/variable")
async def set_variable(self, variable_id: str, value: Any) -> Any:
"""Set a Homey Logic Variable to *value*."""
return await self._put(
f"/api/manager/logic/variable/{variable_id}",
json={"value": value},
)
# ---- REST: notifications ---------------------------------------------
async def send_notification(self, title: str, excerpt: str = "") -> Any:
"""Push a notification to the Homey app on all registered phones."""
return await self._post(
"/api/manager/notifications/notification",
json={
"ownerUri": "homey:manager:notifications",
"title": title,
"excerpt": excerpt,
},
)
# ---- realtime events --------------------------------------------------
def recent_events(
self, limit: int = 50, kind: str | None = None
) -> list[dict[str, Any]]:
items = list(self._events)
if kind:
items = [e for e in items if e.get("kind") == kind]
return items[-limit:]
async def start_realtime(self) -> None:
"""Best-effort Socket.IO connection. Failures are logged, not raised."""
if not self.settings.realtime:
return
if socketio is None:
log.warning("python-socketio not installed; realtime disabled")
return
if self._sio_connected:
return
sio = socketio.AsyncClient(reconnection=True, logger=False, engineio_logger=False)
self._sio = sio
def _record(kind: str) -> Any:
async def _handler(data: Any = None) -> None:
self._events.append(
{"ts": time.time(), "kind": kind, "data": data}
)
return _handler
for kind in (
"device.create",
"device.update",
"device.delete",
"capability",
):
sio.on(kind, _record(kind))
try:
await sio.connect(
self.settings.base_url,
auth={"token": self.settings.token},
transports=["websocket"],
wait_timeout=self.settings.timeout,
)
self._sio_connected = True
log.info("Socket.IO connected to %s", self.settings.base_url)
except Exception as exc: # pragma: no cover
log.warning("Socket.IO connection failed: %s", exc)
self._sio_connected = False
File diff suppressed because it is too large. Load diff