zwei Lampen und Test-Werkzeug

This commit is contained in:
2025-10-31 15:17:28 +01:00
parent ea17d048ad
commit c3ec6e3fc4
7 changed files with 949 additions and 43 deletions

View File

@@ -43,6 +43,11 @@ def load_config(config_path: Path) -> dict[str, Any]:
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))
logger.info(f"Loaded configuration from {config_path}")
return config
@@ -56,16 +61,33 @@ def validate_devices(devices: list[dict[str, Any]]) -> None:
Raises:
ValueError: If device configuration is invalid
"""
required_fields = ["device_id", "type", "cap_version", "technology"]
for device in devices:
if "id" not in device:
raise ValueError(f"Device missing 'id': {device}")
if "type" not in device:
raise ValueError(f"Device {device['id']} missing 'type'")
# Check for device_id
if "device_id" not in device or device["device_id"] is None:
raise ValueError(f"Device entry requires 'id' or 'device_id': {device}")
device_id = device["device_id"]
# Check required top-level fields
for field in required_fields:
if field not in device:
raise ValueError(f"Device {device_id} missing '{field}'")
# Check topics structure
if "topics" not in device:
raise ValueError(f"Device {device['id']} missing 'topics'")
if "set" not in device["topics"] or "state" not in device["topics"]:
raise ValueError(f"Device {device['id']} missing 'topics.set' or 'topics.state'")
logger.info(f"Validated {len(devices)} device(s)")
raise ValueError(f"Device {device_id} missing 'topics'")
if "set" not in device["topics"]:
raise ValueError(f"Device {device_id} missing 'topics.set'")
if "state" not in device["topics"]:
raise ValueError(f"Device {device_id} missing 'topics.state'")
# Log loaded devices
device_ids = [d["device_id"] for d in devices]
logger.info(f"Loaded {len(devices)} device(s): {', '.join(device_ids)}")
async def get_redis_client(redis_url: str, max_retries: int = 5) -> aioredis.Redis:
@@ -172,7 +194,7 @@ async def mqtt_worker(config: dict[str, Any], redis_client: aioredis.Redis) -> N
redis_config = config.get("redis", {})
redis_channel = redis_config.get("channel", "ui:updates")
devices = {d["id"]: d for d in config.get("devices", [])}
devices = {d["device_id"]: d for d in config.get("devices", [])}
retry_delay = 1
max_retry_delay = 60
@@ -191,7 +213,7 @@ async def mqtt_worker(config: dict[str, Any], redis_client: aioredis.Redis) -> N
# Subscribe to abstract SET topics for all devices
for device in devices.values():
abstract_set_topic = f"home/{device['type']}/{device['id']}/set"
abstract_set_topic = f"home/{device['type']}/{device['device_id']}/set"
await client.subscribe(abstract_set_topic)
logger.info(f"Subscribed to abstract SET: {abstract_set_topic}")