Compare commits

...

17 Commits

Author SHA1 Message Date
fb2eef2a42 aid in homekit
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-11 11:51:54 +01:00
0a2007ee65 config file loading 2
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-11 11:40:04 +01:00
bdb25e3550 config file loading
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-11 11:37:00 +01:00
6c284fa1f6 add homekit_aid and load it
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-11 10:32:53 +01:00
5346d1b72c licht flur haustuer
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-11 09:29:38 +01:00
d8780b1790 herdlicht 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 21:41:35 +01:00
3d5010b4a1 herdlicht
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 21:40:13 +01:00
b471ab5edc hottis wifi relay 4
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-10 21:26:19 +01:00
3e0a1b49ab hottis wifi relay 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 21:21:29 +01:00
befdc8a46c hottis wifi relay 2
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-10 21:15:49 +01:00
da16c59238 hottis wifi relay
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-10 21:13:00 +01:00
5f3185894d licht keller flur 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 20:58:45 +01:00
fb828c9a2c licht keller flur 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 20:50:34 +01:00
064ee6bbed licht keller flur
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-10 20:47:40 +01:00
d39bcfce26 excluded 2
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-09 17:38:46 +01:00
1fd275186a excluded
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-09 17:28:32 +01:00
da370c9050 room id
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/7 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/6 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-09 17:13:51 +01:00
12 changed files with 675 additions and 86 deletions

View File

@@ -19,6 +19,7 @@ from apps.abstraction.vendors import (
tasmota,
hottis_pv_modbus,
hottis_wago_modbus,
hottis_wifi_relay,
)
logger = logging.getLogger(__name__)
@@ -42,6 +43,7 @@ for vendor_name, vendor_module in [
("tasmota", tasmota),
("hottis_pv_modbus", hottis_pv_modbus),
("hottis_wago_modbus", hottis_wago_modbus),
("hottis_wifi_relay", hottis_wifi_relay),
]:
for (device_type, direction), handler in vendor_module.HANDLERS.items():
key = (device_type, vendor_name, direction)

View File

@@ -0,0 +1,38 @@
"""Hottis WiFi Relay vendor transformations."""
import logging
from typing import Any
logger = logging.getLogger(__name__)
def transform_relay_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract relay payload to Hottis WiFi Relay format.
Hottis WiFi Relay expects plain text 'on' or 'off' (not JSON).
Example:
- Abstract: {'power': 'on'}
- Hottis WiFi Relay: 'ON'
"""
power = payload.get("power", "off").upper()
return power
def transform_relay_to_abstract(payload: str) -> dict[str, Any]:
"""Transform Hottis WiFi Relay relay payload to abstract format.
Hottis WiFi Relay sends plain text 'on' or 'off'.
Example:
- Hottis WiFi Relay: 'ON'
- Abstract: {'power': 'on'}
"""
return {"power": payload.strip().lower()}
# Registry of handlers for this vendor
HANDLERS = {
("relay", "to_vendor"): transform_relay_to_vendor,
("relay", "to_abstract"): transform_relay_to_abstract,
}

View File

@@ -8,9 +8,9 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
MQTT_BROKER=172.16.2.16 \
MQTT_PORT=1883 \
REDIS_HOST=localhost \
REDIS_HOST=172.23.1.116 \
REDIS_PORT=6379 \
REDIS_DB=0 \
REDIS_DB=8 \
REDIS_CHANNEL=ui:updates
# Create non-root user

146
apps/api/config.py Normal file
View File

@@ -0,0 +1,146 @@
"""Configuration loading and caching for API application.
This module provides centralized configuration management for devices and layout,
with startup validation and in-memory caching for performance.
"""
import logging
from pathlib import Path
from typing import Any
import yaml
from packages.home_capabilities.layout import UiLayout
logger = logging.getLogger(__name__)
# Global caches (loaded once at startup)
devices_cache: list[dict[str, Any]] = []
layout_cache: UiLayout | None = None
def load_devices_from_file() -> list[dict[str, Any]]:
"""Load devices from configuration file and validate.
Returns:
list: List of device configurations
Raises:
FileNotFoundError: If devices.yaml doesn't exist
KeyError: If any device is missing required homekit_aid field
ValueError: If devices.yaml is invalid or contains duplicate homekit_aid values
"""
config_path = Path(__file__).parent.parent.parent / "config" / "devices.yaml"
if not config_path.exists():
raise FileNotFoundError(f"devices.yaml not found at {config_path}")
with open(config_path, "r") as f:
config = yaml.safe_load(f)
if not config or "devices" not in config:
raise ValueError("devices.yaml must contain 'devices' key")
# Normalize device entries: accept both 'id' and 'device_id', use 'device_id' internally
devices = config.get("devices", [])
for device in devices:
device["device_id"] = device.pop("device_id", device.pop("id", None))
# Validate required homekit_aid field
if "homekit_aid" not in device:
raise KeyError(f"Device {device.get('device_id', 'unknown')} is missing required 'homekit_aid' field")
# Validate unique homekit_aid values
aids = [d["homekit_aid"] for d in devices]
if len(aids) != len(set(aids)):
duplicates = [aid for aid in aids if aids.count(aid) > 1]
raise ValueError(f"Duplicate homekit_aid values found: {set(duplicates)}")
logger.info(f"Loaded {len(devices)} devices with unique homekit_aid values (range: {min(aids)}-{max(aids)})")
return devices
def load_layout_from_file() -> UiLayout:
"""Load UI layout from configuration file and validate.
Returns:
UiLayout: Parsed and validated layout configuration
Raises:
FileNotFoundError: If layout.yaml doesn't exist
ValueError: If layout validation fails
yaml.YAMLError: If YAML parsing fails
"""
config_path = Path(__file__).parent.parent.parent / "config" / "layout.yaml"
if not config_path.exists():
raise FileNotFoundError(
f"Layout configuration not found: {config_path}. "
f"Please create a layout.yaml file with room and device definitions."
)
try:
with open(config_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
except yaml.YAMLError as e:
raise yaml.YAMLError(f"Failed to parse YAML in {config_path}: {e}")
if data is None:
raise ValueError(f"Layout file is empty: {config_path}")
try:
layout = UiLayout(**data)
except Exception as e:
raise ValueError(f"Invalid layout configuration in {config_path}: {e}")
total_devices = layout.total_devices()
room_names = [room.name for room in layout.rooms]
logger.info(
f"Loaded layout: {len(layout.rooms)} rooms, "
f"{total_devices} total devices (Rooms: {', '.join(room_names)})"
)
return layout
def load_devices() -> list[dict[str, Any]]:
"""Get devices from in-memory cache.
Returns:
list: List of device configurations (loaded at startup)
"""
return devices_cache
def load_layout() -> UiLayout:
"""Get layout from in-memory cache.
Returns:
UiLayout: Layout configuration (loaded at startup)
Raises:
RuntimeError: If layout cache is not initialized
"""
if layout_cache is None:
raise RuntimeError("Layout cache not initialized. Application startup may have failed.")
return layout_cache
def initialize_config() -> None:
"""Initialize configuration by loading devices and layout.
This function should be called once during application startup.
Raises:
Exception: If configuration loading or validation fails
"""
global devices_cache, layout_cache
# Load devices with validation
devices_cache = load_devices_from_file()
# Load layout with validation
layout_cache = load_layout_from_file()
logger.info("Configuration initialization complete")

View File

@@ -24,9 +24,11 @@ from packages.home_capabilities import (
ContactState,
TempHumidityState,
RelayState,
load_layout,
)
# Import configuration management
from apps.api.config import initialize_config, load_devices, load_layout
# Import resolvers (must be before router imports to avoid circular dependency)
from apps.api.resolvers import (
DeviceDTO,
@@ -99,30 +101,6 @@ async def get_device_state(device_id: str):
except KeyError:
raise HTTPException(status_code=404, detail="Device state not found")
# --- Minimal-invasive: Einzelgerät-Layout-Endpunkt ---
@app.get("/devices/{device_id}/layout")
async def get_device_layout(device_id: str):
"""Gibt die layout-spezifischen Informationen für ein einzelnes Gerät zurück."""
layout = load_layout()
for room in layout.get("rooms", []):
for device in room.get("devices", []):
if device.get("device_id") == device_id:
# Rückgabe: Layout-Infos + Raumname
return {
"device_id": device_id,
"room": room.get("name"),
"title": device.get("title"),
"icon": device.get("icon"),
"rank": device.get("rank"),
}
raise HTTPException(status_code=404, detail="Device layout not found")
@app.on_event("startup")
async def startup_event():
"""Include routers after app is initialized to avoid circular imports."""
from apps.api.routes.groups_scenes import router as groups_scenes_router
app.include_router(groups_scenes_router, prefix="")
@app.get("/health")
async def health() -> dict[str, str]:
@@ -188,6 +166,21 @@ async def redis_state_listener():
async def startup_event():
"""Start background tasks on application startup."""
global background_task
# Include routers
from apps.api.routes.groups_scenes import router as groups_scenes_router
from apps.api.routes.rooms import router as rooms_router
app.include_router(groups_scenes_router, prefix="")
app.include_router(rooms_router, prefix="")
# Load and validate configuration (devices + layout)
try:
initialize_config()
except Exception as e:
logger.error(f"Failed to initialize configuration: {e}")
raise # Fatal error - application will not start
background_task = asyncio.create_task(redis_state_listener())
logger.info("Started background Redis state listener")
@@ -235,32 +228,11 @@ class DeviceInfo(BaseModel):
device_id: str
type: str
name: str
homekit_aid: int
features: dict[str, Any] = {}
# Configuration helpers
def load_devices() -> list[dict[str, Any]]:
"""Load devices from configuration file.
Returns:
list: List of device configurations
"""
config_path = Path(__file__).parent.parent.parent / "config" / "devices.yaml"
if not config_path.exists():
return []
with open(config_path, "r") as f:
config = yaml.safe_load(f)
# Normalize device entries: accept both 'id' and 'device_id', use 'device_id' internally
devices = config.get("devices", [])
for device in devices:
device["device_id"] = device.pop("device_id", device.pop("id", None))
return devices
def get_mqtt_settings() -> tuple[str, int]:
"""Get MQTT broker settings from environment.
@@ -388,6 +360,7 @@ async def get_device(device_id: str) -> DeviceInfo:
device_id=device["device_id"],
type=device["type"],
name=device.get("name", device["device_id"]),
homekit_aid=device["homekit_aid"],
features=device.get("features", {})
)
@@ -406,6 +379,7 @@ async def get_devices() -> list[DeviceInfo]:
device_id=device["device_id"],
type=device["type"],
name=device.get("name", device["device_id"]),
homekit_aid=device["homekit_aid"],
features=device.get("features", {})
)
for device in devices

View File

@@ -4,12 +4,12 @@ import logging
from pathlib import Path
from typing import Any, TypedDict
from apps.api.config import load_layout
from packages.home_capabilities import (
GroupConfig,
GroupsConfigRoot,
SceneStep,
get_group_by_id,
load_layout,
)
logger = logging.getLogger(__name__)

219
apps/api/routes/rooms.py Normal file
View File

@@ -0,0 +1,219 @@
"""
Room-based device control endpoints.
Provides bulk control operations for devices within rooms:
- /rooms/{room_name}/lights - Control all lights in a room
- /rooms/{room_name}/heating - Control all thermostats in a room
"""
import logging
from typing import Any
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from apps.api.config import load_layout
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Rooms"])
@router.get("/rooms")
async def get_rooms() -> list[dict[str, str]]:
"""Get list of all room IDs and names.
Returns:
List of dicts with room id and name
"""
layout = load_layout()
return [
{
"id": room.id,
"name": room.name
}
for room in layout.rooms
]
class LightsControlRequest(BaseModel):
"""Request model for controlling lights in a room."""
power: str # "on" or "off"
brightness: int | None = None # Optional brightness 0-100
class HeatingControlRequest(BaseModel):
"""Request model for controlling heating in a room."""
target: float # Target temperature
def get_room_devices(room_id: str) -> list[dict[str, Any]]:
"""Get all devices in a specific room from layout.
Args:
room_id: ID of the room
Returns:
List of device dicts with device_id, title, icon, rank, excluded
Raises:
HTTPException: If room not found
"""
layout = load_layout()
for room in layout.rooms:
if room.id == room_id:
return [
{
"device_id": device.device_id,
"title": device.title,
"icon": device.icon,
"rank": device.rank,
"excluded": device.excluded
}
for device in room.devices
]
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Room '{room_id}' not found"
)
@router.post("/rooms/{room_id}/lights", status_code=status.HTTP_202_ACCEPTED)
async def control_room_lights(room_id: str, request: LightsControlRequest) -> dict[str, Any]:
"""Control all lights (light and relay devices) in a room.
Args:
room_id: ID of the room
request: Light control parameters
Returns:
dict with affected device_ids and command summary
"""
from apps.api.main import load_devices, publish_abstract_set
# Get all devices in room
room_devices = get_room_devices(room_id)
# Filter out excluded devices
room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)}
# Load all devices to filter by type
all_devices = load_devices()
# Filter for light/relay devices in this room
light_devices = [
d for d in all_devices
if d["device_id"] in room_device_ids and d["type"] in ("light", "relay")
]
if not light_devices:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"No light devices found in room '{room_id}'"
)
# Build payload
payload = {"power": request.power}
if request.brightness is not None and request.power == "on":
payload["brightness"] = request.brightness
# Send commands to all light devices
affected_ids = []
errors = []
for device in light_devices:
try:
await publish_abstract_set(
device_type=device["type"],
device_id=device["device_id"],
payload=payload
)
affected_ids.append(device["device_id"])
logger.info(f"Sent command to {device['device_id']}: {payload}")
except Exception as e:
logger.error(f"Failed to control {device['device_id']}: {e}")
errors.append({
"device_id": device["device_id"],
"error": str(e)
})
return {
"room": room_id,
"command": "lights",
"payload": payload,
"affected_devices": affected_ids,
"success_count": len(affected_ids),
"error_count": len(errors),
"errors": errors if errors else None
}
@router.post("/rooms/{room_id}/heating", status_code=status.HTTP_202_ACCEPTED)
async def control_room_heating(room_id: str, request: HeatingControlRequest) -> dict[str, Any]:
"""Control all thermostats in a room.
Args:
room_id: ID of the room
request: Heating control parameters
Returns:
dict with affected device_ids and command summary
"""
from apps.api.main import load_devices, publish_abstract_set
# Get all devices in room
room_devices = get_room_devices(room_id)
# Filter out excluded devices
room_device_ids = {d["device_id"] for d in room_devices if not d.get("excluded", False)}
# Load all devices to filter by type
all_devices = load_devices()
# Filter for thermostat devices in this room
thermostat_devices = [
d for d in all_devices
if d["device_id"] in room_device_ids and d["type"] == "thermostat"
]
if not thermostat_devices:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"No thermostat devices found in room '{room_name}'"
)
# Build payload
payload = {"target": request.target}
# Send commands to all thermostat devices
affected_ids = []
errors = []
for device in thermostat_devices:
try:
await publish_abstract_set(
device_type="thermostat",
device_id=device["device_id"],
payload=payload
)
affected_ids.append(device["device_id"])
logger.info(f"Sent heating command to {device['device_id']}: {payload}")
except Exception as e:
logger.error(f"Failed to control {device['device_id']}: {e}")
errors.append({
"device_id": device["device_id"],
"error": str(e)
})
return {
"room": room_id,
"command": "heating",
"payload": payload,
"affected_devices": affected_ids,
"success_count": len(affected_ids),
"error_count": len(errors),
"errors": errors if errors else None
}

View File

@@ -18,6 +18,7 @@ class Device:
device_id: str
type: str # "light", "thermostat", "relay", "contact", "temp_humidity", "cover"
name: str # Short name from /devices
homekit_aid: int # HomeKit Accessory ID
features: Dict[str, bool] # Feature flags (e.g., {"power": true, "brightness": true})
read_only: bool # True for sensors that don't accept commands
@@ -57,6 +58,12 @@ class DeviceRegistry:
logger.warning(f"Device without device_id: {dev_data}")
continue
# Check for required homekit_aid field
homekit_aid = dev_data.get('homekit_aid')
if homekit_aid is None:
logger.error(f"Device {device_id} is missing required homekit_aid field - skipping")
continue
# Determine if read-only (sensors don't accept set commands)
device_type = dev_data.get('type', '')
read_only = device_type in ['contact', 'temp_humidity', 'motion', 'smoke']
@@ -64,7 +71,8 @@ class DeviceRegistry:
device = Device(
device_id=device_id,
type=device_type,
name=device_id,
name=dev_data.get('name', device_id),
homekit_aid=homekit_aid,
features=dev_data.get('features', {}),
read_only=read_only
)

View File

@@ -71,9 +71,11 @@ def build_bridge(driver: AccessoryDriver, api_client: ApiClient) -> Bridge:
try:
accessory = create_accessory_for_device(device, api_client, driver)
if accessory:
# Set AID from device configuration
accessory.aid = device.homekit_aid
bridge.add_accessory(accessory)
accessory_map[device.device_id] = accessory
logger.info(f"Added accessory: {device.name} ({device.type}, {accessory.__class__.__name__})")
logger.info(f"Added accessory: {device.name} ({device.type}, AID={device.homekit_aid}, {accessory.__class__.__name__})")
else:
logger.warning(f"No accessory mapping for device: {device.name} ({device.type})")
except Exception as e:

View File

@@ -1,6 +1,7 @@
version: 1
devices:
- device_id: lampe_semeniere_wohnzimmer
homekit_aid: 2
name: Semeniere
type: relay
cap_version: "relay@1.0.0"
@@ -16,6 +17,7 @@ devices:
model: "AC10691"
vendor: "OSRAM"
- device_id: stehlampe_esszimmer_spiegel
homekit_aid: 3
name: Stehlampe Spiegel
type: light
cap_version: "light@1.2.0"
@@ -27,6 +29,7 @@ devices:
state: "zigbee2mqtt/0x001788010d06ea09"
set: "zigbee2mqtt/0x001788010d06ea09/set"
- device_id: stehlampe_esszimmer_schrank
homekit_aid: 4
name: Stehlampe Schrank
type: light
cap_version: "light@1.2.0"
@@ -38,6 +41,7 @@ devices:
state: "zigbee2mqtt/0x001788010d09176c"
set: "zigbee2mqtt/0x001788010d09176c/set"
- device_id: grosse_lampe_wohnzimmer
homekit_aid: 5
name: grosse Lampe
type: relay
cap_version: "relay@1.0.0"
@@ -53,6 +57,7 @@ devices:
model: "AC10691"
vendor: "OSRAM"
- device_id: lampe_naehtischchen_wohnzimmer
homekit_aid: 6
name: Nähtischchen
type: relay
cap_version: "relay@1.0.0"
@@ -68,6 +73,7 @@ devices:
model: "HG06337"
vendor: "Lidl"
- device_id: kleine_lampe_links_esszimmer
homekit_aid: 7
name: kleine Lampe
type: relay
cap_version: "relay@1.0.0"
@@ -83,6 +89,7 @@ devices:
model: "AC10691"
vendor: "OSRAM"
- device_id: leselampe_esszimmer
homekit_aid: 8
name: Leselampe
type: light
cap_version: "light@1.2.0"
@@ -99,6 +106,7 @@ devices:
model: "LED1842G3"
vendor: "IKEA"
- device_id: medusalampe_schlafzimmer
homekit_aid: 9
name: Medusa-Lampe
type: relay
cap_version: "relay@1.0.0"
@@ -114,6 +122,7 @@ devices:
model: "AC10691"
vendor: "OSRAM"
- device_id: sportlicht_am_fernseher_studierzimmer
homekit_aid: 10
type: light
name: am Fernseher
cap_version: "light@1.2.0"
@@ -131,6 +140,7 @@ devices:
model: "LED1733G7"
vendor: "IKEA"
- device_id: deckenlampe_schlafzimmer
homekit_aid: 11
name: Deckenlampe
type: light
cap_version: "light@1.2.0"
@@ -147,6 +157,7 @@ devices:
model: "8718699688882"
vendor: "Philips"
- device_id: bettlicht_wolfgang
homekit_aid: 12
name: Bettlicht Wolfgang
type: light
cap_version: "light@1.2.0"
@@ -163,6 +174,7 @@ devices:
model: "9290020399"
vendor: "Philips"
- device_id: bettlicht_patty
homekit_aid: 13
name: Bettlicht Patty
type: light
cap_version: "light@1.2.0"
@@ -179,6 +191,7 @@ devices:
model: "9290020399"
vendor: "Philips"
- device_id: schranklicht_hinten_patty
homekit_aid: 14
name: Schranklicht hinten
type: light
cap_version: "light@1.2.0"
@@ -195,6 +208,7 @@ devices:
model: "8718699673147"
vendor: "Philips"
- device_id: schranklicht_vorne_patty
homekit_aid: 15
name: Schranklicht vorne
type: relay
cap_version: "relay@1.0.0"
@@ -210,6 +224,7 @@ devices:
model: "AC10691"
vendor: "OSRAM"
- device_id: leselampe_patty
homekit_aid: 16
name: Leselampe
type: light
cap_version: "light@1.2.0"
@@ -226,6 +241,7 @@ devices:
model: "8718699673147"
vendor: "Philips"
- device_id: deckenlampe_esszimmer
homekit_aid: 17
name: Deckenlampe
type: light
cap_version: "light@1.2.0"
@@ -242,6 +258,7 @@ devices:
model: "929002241201"
vendor: "Philips"
- device_id: deckenlampe_flur_oben
homekit_aid: 18
name: Deckenlampe oben
type: light
cap_version: "light@1.2.0"
@@ -259,6 +276,7 @@ devices:
model: "929003099001"
vendor: "Philips"
- device_id: kueche_deckenlampe
homekit_aid: 19
name: Deckenlampe
type: light
cap_version: "light@1.2.0"
@@ -275,6 +293,7 @@ devices:
model: "929002469202"
vendor: "Philips"
- device_id: sportlicht_tisch
homekit_aid: 20
name: am Tisch
type: light
cap_version: "light@1.2.0"
@@ -291,6 +310,7 @@ devices:
model: "4058075729063"
vendor: "LEDVANCE"
- device_id: sportlicht_regal
homekit_aid: 21
name: am Regal
type: light
cap_version: "light@1.2.0"
@@ -307,6 +327,7 @@ devices:
model: "4058075729063"
vendor: "LEDVANCE"
- device_id: licht_flur_oben_am_spiegel
homekit_aid: 22
name: Spiegel
type: light
cap_version: "light@1.2.0"
@@ -324,6 +345,7 @@ devices:
model: "LED1732G11"
vendor: "IKEA"
- device_id: experimentlabtest
homekit_aid: 23
name: Test Lampe
type: light
cap_version: "light@1.2.0"
@@ -340,6 +362,7 @@ devices:
model: "4058075208421"
vendor: "LEDVANCE"
- device_id: thermostat_wolfgang
homekit_aid: 24
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -359,6 +382,7 @@ devices:
model: "GS361A-H04"
vendor: "Siterwell"
- device_id: thermostat_kueche
homekit_aid: 25
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -378,6 +402,7 @@ devices:
model: "GS361A-H04"
vendor: "Siterwell"
- device_id: thermostat_schlafzimmer
homekit_aid: 26
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -397,6 +422,7 @@ devices:
peer_id: "42"
channel: "1"
- device_id: thermostat_esszimmer
homekit_aid: 27
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -416,6 +442,7 @@ devices:
peer_id: "45"
channel: "1"
- device_id: thermostat_wohnzimmer
homekit_aid: 28
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -435,6 +462,7 @@ devices:
peer_id: "46"
channel: "1"
- device_id: thermostat_patty
homekit_aid: 29
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -454,6 +482,7 @@ devices:
peer_id: "39"
channel: "1"
- device_id: thermostat_bad_oben
homekit_aid: 30
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -473,6 +502,7 @@ devices:
peer_id: "41"
channel: "1"
- device_id: thermostat_bad_unten
homekit_aid: 31
name: Heizung
type: thermostat
cap_version: "thermostat@1.0.0"
@@ -492,6 +522,7 @@ devices:
peer_id: "48"
channel: "1"
- device_id: sterne_wohnzimmer
homekit_aid: 32
name: Sterne
type: relay
cap_version: "relay@1.0.0"
@@ -507,6 +538,7 @@ devices:
model: "AC10691"
vendor: "OSRAM"
- device_id: kontakt_schlafzimmer_strasse
homekit_aid: 33
name: Fenster
type: contact
cap_version: contact_sensor@1.0.0
@@ -515,6 +547,7 @@ devices:
state: homegear/instance1/plain/52/1/STATE
features: {}
- device_id: kontakt_esszimmer_strasse_rechts
homekit_aid: 34
type: contact
name: Fenster rechts
cap_version: contact_sensor@1.0.0
@@ -523,6 +556,7 @@ devices:
state: homegear/instance1/plain/26/1/STATE
features: {}
- device_id: kontakt_esszimmer_strasse_links
homekit_aid: 35
name: Fenster links
type: contact
cap_version: contact_sensor@1.0.0
@@ -531,6 +565,7 @@ devices:
state: homegear/instance1/plain/27/1/STATE
features: {}
- device_id: kontakt_wohnzimmer_garten_rechts
homekit_aid: 36
name: Fenster rechts
type: contact
cap_version: contact_sensor@1.0.0
@@ -539,6 +574,7 @@ devices:
state: homegear/instance1/plain/28/1/STATE
features: {}
- device_id: kontakt_wohnzimmer_garten_links
homekit_aid: 37
name: Fenster links
type: contact
cap_version: contact_sensor@1.0.0
@@ -547,6 +583,7 @@ devices:
state: homegear/instance1/plain/29/1/STATE
features: {}
- device_id: kontakt_kueche_garten_fenster
homekit_aid: 38
name: Fenster Garten
type: contact
cap_version: contact_sensor@1.0.0
@@ -555,6 +592,7 @@ devices:
state: zigbee2mqtt/0x00158d008b332785
features: {}
- device_id: kontakt_kueche_garten_tuer
homekit_aid: 39
type: contact
name: Terrassentür
cap_version: contact_sensor@1.0.0
@@ -563,6 +601,7 @@ devices:
state: zigbee2mqtt/0x00158d008b332788
features: {}
- device_id: kontakt_kueche_strasse_rechts
homekit_aid: 40
name: Fenster Straße rechts
type: contact
cap_version: contact_sensor@1.0.0
@@ -571,6 +610,7 @@ devices:
state: zigbee2mqtt/0x00158d008b151803
features: {}
- device_id: kontakt_kueche_strasse_links
homekit_aid: 41
name: Fenster Straße links
type: contact
cap_version: contact_sensor@1.0.0
@@ -579,6 +619,7 @@ devices:
state: zigbee2mqtt/0x00158d008b331d0b
features: {}
- device_id: kontakt_patty_garten_rechts
homekit_aid: 42
type: contact
name: Fenster Garten rechts
cap_version: contact_sensor@1.0.0
@@ -587,6 +628,8 @@ devices:
state: homegear/instance1/plain/18/1/STATE
features: {}
- device_id: kontakt_patty_garten_links
homekit_aid: 43
homekit_aid: 43
type: contact
name: Fenster Garten links
cap_version: contact_sensor@1.0.0
@@ -595,6 +638,7 @@ devices:
state: homegear/instance1/plain/22/1/STATE
features: {}
- device_id: kontakt_patty_strasse
homekit_aid: 44
type: contact
name: Fenster Straße
cap_version: contact_sensor@1.0.0
@@ -603,6 +647,7 @@ devices:
state: zigbee2mqtt/0x00158d000af457cf
features: {}
- device_id: kontakt_wolfgang_garten
homekit_aid: 45
type: contact
name: Fenster
cap_version: contact_sensor@1.0.0
@@ -611,6 +656,7 @@ devices:
state: zigbee2mqtt/0x00158d008b3328da
features: {}
- device_id: kontakt_bad_oben_strasse
homekit_aid: 46
type: contact
name: Fenster
cap_version: contact_sensor@1.0.0
@@ -619,6 +665,7 @@ devices:
state: zigbee2mqtt/0x00158d008b333aec
features: {}
- device_id: kontakt_bad_unten_strasse
homekit_aid: 47
type: contact
name: Fenster
cap_version: contact_sensor@1.0.0
@@ -627,6 +674,7 @@ devices:
state: homegear/instance1/plain/44/1/STATE
features: {}
- device_id: sensor_schlafzimmer
homekit_aid: 48
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -635,6 +683,7 @@ devices:
state: zigbee2mqtt/0x00158d00043292dc
features: {}
- device_id: sensor_wohnzimmer
homekit_aid: 49
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -643,6 +692,7 @@ devices:
state: zigbee2mqtt/0x00158d0008975707
features: {}
- device_id: sensor_kueche
homekit_aid: 50
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -651,6 +701,7 @@ devices:
state: zigbee2mqtt/0x00158d00083299bb
features: {}
- device_id: sensor_arbeitszimmer_patty
homekit_aid: 51
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -659,6 +710,7 @@ devices:
state: zigbee2mqtt/0x00158d0003f052b7
features: {}
- device_id: sensor_arbeitszimmer_wolfgang
homekit_aid: 52
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -667,6 +719,7 @@ devices:
state: zigbee2mqtt/0x00158d000543fb99
features: {}
- device_id: sensor_bad_oben
homekit_aid: 53
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -675,6 +728,7 @@ devices:
state: zigbee2mqtt/0x00158d00093e8987
features: {}
- device_id: sensor_bad_unten
homekit_aid: 54
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -683,6 +737,7 @@ devices:
state: zigbee2mqtt/0x00158d00093e662a
features: {}
- device_id: sensor_flur
homekit_aid: 55
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -691,6 +746,7 @@ devices:
state: zigbee2mqtt/0x00158d000836ccc6
features: {}
- device_id: sensor_waschkueche
homekit_aid: 56
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -699,6 +755,7 @@ devices:
state: zigbee2mqtt/0x00158d000449f3bc
features: {}
- device_id: sensor_sportzimmer
homekit_aid: 57
type: temp_humidity_sensor
name: Thermometer
cap_version: temp_humidity_sensor@1.0.0
@@ -707,6 +764,7 @@ devices:
state: zigbee2mqtt/0x00158d0009421422
features: {}
- device_id: licht_spuele_kueche
homekit_aid: 58
name: Spüle
type: relay
cap_version: "relay@1.0.0"
@@ -717,6 +775,7 @@ devices:
set: "shellies/shellyplug-s-DED4E4/relay/0/command"
state: "shellies/shellyplug-s-DED4E4/relay/0"
- device_id: putzlicht_kueche
homekit_aid: 59
name: Putzlicht
type: light
cap_version: "light@1.2.0"
@@ -728,6 +787,7 @@ devices:
state: "zigbee2mqtt/0xa4c138563834406c"
set: "zigbee2mqtt/0xa4c138563834406c/set"
- device_id: licht_schrank_esszimmer
homekit_aid: 60
name: Schrank
type: relay
cap_version: "relay@1.0.0"
@@ -738,6 +798,7 @@ devices:
set: "shellies/schrankesszimmer/relay/0/command"
state: "shellies/schrankesszimmer/relay/0"
- device_id: licht_regal_wohnzimmer
homekit_aid: 61
type: relay
name: Regal
cap_version: "relay@1.0.0"
@@ -747,17 +808,8 @@ devices:
topics:
set: "shellies/wohnzimmer-regal/relay/0/command"
state: "shellies/wohnzimmer-regal/relay/0"
- device_id: licht_flur_schrank
type: relay
name: Schrank
cap_version: "relay@1.0.0"
technology: shelly
features:
power: true
topics:
set: "shellies/schrankflur/relay/0/command"
state: "shellies/schrankflur/relay/0"
- device_id: licht_terasse
homekit_aid: 62
name: Terrasse
type: relay
cap_version: "relay@1.0.0"
@@ -768,6 +820,7 @@ devices:
set: "shellies/lichtterasse/relay/0/command"
state: "shellies/lichtterasse/relay/0"
- device_id: kugellampe_patty
homekit_aid: 63
name: Kugellampe Patty
type: light
cap_version: "light@1.2.0"
@@ -779,6 +832,7 @@ devices:
state: "zigbee2mqtt/0xbc33acfffe21f547"
set: "zigbee2mqtt/0xbc33acfffe21f547/set"
- device_id: kueche_fensterbank_licht
homekit_aid: 64
name: Fensterbank Küche
type: light
cap_version: "light@1.2.0"
@@ -790,6 +844,7 @@ devices:
state: "zigbee2mqtt/0xf0d1b8000017515d"
set: "zigbee2mqtt/0xf0d1b8000017515d/set"
- device_id: licht_kommode_schlafzimmer
homekit_aid: 65
name: Kommode Schlafzimmer
type: relay
cap_version: "relay@1.0.0"
@@ -800,6 +855,7 @@ devices:
set: "cmnd/tasmota/04/POWER"
state: "stat/tasmota/04/POWER"
- device_id: licht_fensterbank_esszimmer
homekit_aid: 66
name: Fensterbank Esszimmer
type: relay
cap_version: "relay@1.0.0"
@@ -810,6 +866,7 @@ devices:
set: "cmnd/tasmota/02/POWER"
state: "stat/tasmota/02/POWER"
- device_id: licht_schreibtisch_patty
homekit_aid: 67
name: Schreibtisch Patty
type: relay
cap_version: "relay@1.0.0"
@@ -820,6 +877,7 @@ devices:
set: "cmnd/tasmota/03/POWER"
state: "stat/tasmota/03/POWER"
- device_id: kugeln_regal_flur
homekit_aid: 68
name: Kugeln Regal Flur
type: relay
cap_version: "relay@1.0.0"
@@ -829,7 +887,8 @@ devices:
topics:
set: "cmnd/tasmota/01/POWER"
state: "stat/tasmota/01/POWER"
- device_id: schrank_flur_haustür
- device_id: schrank_flur_haustuer
homekit_aid: 69
name: Schrank Flur Haustür
type: relay
cap_version: "relay@1.0.0"
@@ -840,6 +899,7 @@ devices:
set: "cmnd/tasmota/05/POWER"
state: "stat/tasmota/05/POWER"
- device_id: gartenlicht_vorne
homekit_aid: 70
name: Gartenlicht vorne
type: relay
cap_version: "relay@1.0.0"
@@ -851,6 +911,7 @@ devices:
state: "stat/tasmota/06/POWER"
- device_id: power_relay_caroutlet
homekit_aid: 71
name: Car Outlet
type: relay
cap_version: "relay@1.0.0"
@@ -861,6 +922,7 @@ devices:
set: "IoT/Car/Control"
state: "IoT/Car/Control/State"
- device_id: powermeter_caroutlet
homekit_aid: 72
name: Car Outlet
type: three_phase_powermeter
cap_version: "three_phase_powermeter@1.0.0"
@@ -868,6 +930,7 @@ devices:
topics:
state: "IoT/Car/Values"
- device_id: sensor_caroutlet
homekit_aid: 73
name: Car Outlet
type: contact
cap_version: contact_sensor@1.0.0
@@ -876,6 +939,7 @@ devices:
state: IoT/Car/Feedback/State
- device_id: schranklicht_flur_vor_kueche
homekit_aid: 74
name: Schranklicht Flur vor Küche
type: light
cap_version: "relay@1.0.0"
@@ -886,6 +950,7 @@ devices:
state: "zigbee2mqtt/0xf0d1b80000155a1f"
set: "zigbee2mqtt/0xf0d1b80000155a1f/set"
- device_id: deckenlampe_wohnzimmer
homekit_aid: 75
name: Deckenlampe Wohnzimmer
type: light
cap_version: "relay@1.0.0"
@@ -898,3 +963,82 @@ devices:
set: "zigbee2mqtt/0x842e14fffea72027/set"
- device_id: keller_flur_licht
homekit_aid: 76
name: Keller Flur Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/10/21"
state: "pulsegen/status/10"
- device_id: waschkueche_licht
homekit_aid: 77
name: Waschküche Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/8/22"
state: "pulsegen/status/8"
- device_id: werkstatt_licht
homekit_aid: 78
name: Werkstatt Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/7/19"
state: "pulsegen/status/7"
- device_id: sportzimmer_licht
homekit_aid: 79
name: Sportzimmer Licht
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/9/20"
state: "pulsegen/status/9"
- device_id: deckenlampe_patty
homekit_aid: 80
name: Deckenlampe Patty
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wago_modbus
features:
power: true
topics:
set: "pulsegen/command/4/16"
state: "pulsegen/status/4"
- device_id: regallampe_esszimmer
homekit_aid: 81
name: Regallampe Esszimmer
type: relay
cap_version: "relay@1.0.0"
technology: hottis_wifi_relay
features:
power: true
topics:
set: "IoT/WifiRelay1/State"
state: "IoT/WifiRelay1/State"
- device_id: herdlicht
homekit_aid: 82
name: Herdlicht
type: light
cap_version: "relay@1.0.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/herdlicht"
set: "zigbee2mqtt/herdlicht/set"

View File

@@ -1,5 +1,6 @@
rooms:
- name: Schlafzimmer
- id: schlafzimmer
name: Schlafzimmer
devices:
- device_id: bettlicht_patty
title: Bettlicht Patty
@@ -33,7 +34,8 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 47
- name: Esszimmer
- id: esszimmer
name: Esszimmer
devices:
- device_id: deckenlampe_esszimmer
title: Deckenlampe Esszimmer
@@ -59,10 +61,10 @@ rooms:
title: Stehlampe Esszimmer Schrank
icon: 💡
rank: 82
# - device_id: kleine_lampe_rechts_esszimmer
# title: kleine Lampe rechts Esszimmer
# icon: 💡
# rank: 90
- device_id: regallampe_esszimmer
title: Regallampe Esszimmer
icon: 💡
rank: 90
- device_id: licht_schrank_esszimmer
title: Schranklicht Esszimmer
icon: 💡
@@ -79,7 +81,8 @@ rooms:
title: Kontakt Straße links
icon: 🪟
rank: 97
- name: Wohnzimmer
- id: wohnzimmer
name: Wohnzimmer
devices:
- device_id: lampe_naehtischchen_wohnzimmer
title: Lampe Naehtischchen Wohnzimmer
@@ -121,7 +124,8 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 138
- name: che
- id: kueche
name: Küche
devices:
- device_id: kueche_deckenlampe
title: Küche Deckenlampe
@@ -135,10 +139,15 @@ rooms:
title: Küche Putzlicht
icon: 💡
rank: 143
excluded: true
- device_id: kueche_fensterbank_licht
title: Küche Fensterbank
icon: 💡
rank: 144
- device_id: herdlicht
title: Herdlicht
icon: 💡
rank: 145
- device_id: thermostat_kueche
title: Kueche
icon: 🌡️
@@ -163,30 +172,35 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 155
- name: Arbeitszimmer Patty
- id: arbeitszimmer_patty
name: Arbeitszimmer Patty
devices:
- device_id: leselampe_patty
title: Leselampe Patty
icon: 💡
rank: 160
- device_id: schranklicht_hinten_patty
title: Schranklicht hinten Patty
title: Schranklicht hinten
icon: 💡
rank: 170
- device_id: schranklicht_vorne_patty
title: Schranklicht vorne Patty
title: Schranklicht vorne
icon: 💡
rank: 180
- device_id: kugellampe_patty
title: Kugellampe Patty
title: Kugellampe
icon: 💡
rank: 181
- device_id: licht_schreibtisch_patty
title: Licht Schreibtisch Patty
title: Licht Schreibtisch
icon: 💡
rank: 182
- device_id: deckenlampe_patty
title: Deckenlampe
icon: 💡
rank: 183
- device_id: thermostat_patty
title: Thermostat Patty
title: Thermostat
icon: 🌡️
rank: 185
- device_id: kontakt_patty_garten_rechts
@@ -205,7 +219,8 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 189
- name: Arbeitszimmer Wolfgang
- id: arbeitszimmer_wolfgang
name: Arbeitszimmer Wolfgang
devices:
- device_id: thermostat_wolfgang
title: Wolfgang
@@ -223,7 +238,8 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 202
- name: Flur
- id: flur
name: Flur
devices:
- device_id: deckenlampe_flur_oben
title: Deckenlampe Flur oben
@@ -237,7 +253,7 @@ rooms:
title: Licht oben am Spiegel
icon: 💡
rank: 230
- device_id: schrank_flur_haustür
- device_id: schrank_flur_haustuer
title: Schranklicht an der Haustür
icon: 💡
rank: 231
@@ -249,7 +265,8 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 235
- name: Sportzimmer
- id: sportzimmer
name: Sportzimmer
devices:
- device_id: sportlicht_regal
title: Sportlicht Regal
@@ -263,11 +280,16 @@ rooms:
title: Sportlicht am Fernseher, Studierzimmer
icon: 🏃
rank: 260
- device_id: sportzimmer_licht
title: Deckenlampe
icon: 💡
rank: 262
- device_id: sensor_sportzimmer
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 265
- name: Bad Oben
- id: bad_oben
name: Bad Oben
devices:
- device_id: thermostat_bad_oben
title: Thermostat Bad Oben
@@ -281,7 +303,8 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 272
- name: Bad Unten
- id: bad_unten
name: Bad Unten
devices:
- device_id: thermostat_bad_unten
title: Thermostat Bad Unten
@@ -295,13 +318,20 @@ rooms:
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 282
- name: Waschküche
- id: waschkueche
name: Waschküche
devices:
- device_id: sensor_waschkueche
title: Temperatur & Luftfeuchte
icon: 🌡️
rank: 290
- name: Outdoor
- device_id: waschkueche_licht
title: Waschküche Licht
icon: 💡
rank: 340
- id: outdoor
name: Outdoor
devices:
- device_id: licht_terasse
title: Licht Terasse
@@ -311,7 +341,8 @@ rooms:
title: Gartenlicht vorne
icon: 💡
rank: 291
- name: Garage
- id: garage
name: Garage
devices:
- device_id: power_relay_caroutlet
title: Ladestrom
@@ -325,5 +356,18 @@ rooms:
title: Messwerte
icon: 📊
rank: 320
- id: keller
name: Keller
devices:
- device_id: keller_flur_licht
title: Keller Flur Licht
icon: 💡
rank: 330
- device_id: werkstatt_licht
title: Werkstatt Licht
icon: 💡
rank: 350

View File

@@ -18,6 +18,7 @@ class DeviceTile(BaseModel):
title: Display title for the device
icon: Icon name or emoji for the device
rank: Sort order within the room (lower = first)
excluded: Optional flag to exclude device from certain operations
"""
device_id: str = Field(
@@ -41,15 +42,26 @@ class DeviceTile(BaseModel):
description="Sort order (lower values appear first)"
)
excluded: bool = Field(
default=False,
description="Exclude device from bulk operations"
)
class Room(BaseModel):
"""Represents a room containing devices.
Attributes:
id: Unique room identifier (used for API endpoints)
name: Room name (e.g., "Wohnzimmer", "Küche")
devices: List of device tiles in this room
"""
id: str = Field(
...,
description="Unique room identifier"
)
name: str = Field(
...,
description="Room name"