139 lines
4.5 KiB
Python
139 lines
4.5 KiB
Python
"""
|
|
Device Registry for HomeKit Bridge
|
|
|
|
Loads devices from API and joins with layout information.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, List, Optional
|
|
from dataclasses import dataclass
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class Device:
|
|
"""Represents a device with combined info from /devices and /layout."""
|
|
|
|
device_id: str
|
|
type: str # "light", "thermostat", "relay", "contact", "temp_humidity", "cover"
|
|
name: str # Short name from /devices
|
|
friendly_name: str # Display title from /layout (fallback to name)
|
|
room: Optional[str] # Room name from layout
|
|
features: Dict[str, bool] # Feature flags (e.g., {"power": true, "brightness": true})
|
|
read_only: bool # True for sensors that don't accept commands
|
|
|
|
|
|
class DeviceRegistry:
|
|
"""Registry of all devices loaded from the API."""
|
|
|
|
def __init__(self, devices: List[Device]):
|
|
"""
|
|
Initialize registry with devices.
|
|
|
|
Args:
|
|
devices: List of Device objects
|
|
"""
|
|
self._devices = devices
|
|
self._by_id = {d.device_id: d for d in devices}
|
|
|
|
@classmethod
|
|
def load_from_api(cls, api_client) -> 'DeviceRegistry':
|
|
"""
|
|
Load devices from API and join with layout information.
|
|
|
|
Args:
|
|
api_client: ApiClient instance
|
|
|
|
Returns:
|
|
DeviceRegistry with all devices
|
|
"""
|
|
# Get devices and layout
|
|
devices_data = api_client.get_devices()
|
|
layout_data = api_client.get_layout()
|
|
|
|
# Build lookup: device_id -> (room_name, title)
|
|
layout_map = {}
|
|
if isinstance(layout_data, dict) and 'rooms' in layout_data:
|
|
rooms_list = layout_data['rooms']
|
|
if isinstance(rooms_list, list):
|
|
for room in rooms_list:
|
|
if isinstance(room, dict):
|
|
room_name = room.get('name', 'Unknown')
|
|
devices_in_room = room.get('devices', [])
|
|
for device_info in devices_in_room:
|
|
if isinstance(device_info, dict):
|
|
device_id = device_info.get('device_id')
|
|
title = device_info.get('title', '')
|
|
if device_id:
|
|
layout_map[device_id] = (room_name, title)
|
|
|
|
# Create Device objects
|
|
devices = []
|
|
for dev_data in devices_data:
|
|
device_id = dev_data.get('device_id')
|
|
if not device_id:
|
|
logger.warning(f"Device without device_id: {dev_data}")
|
|
continue
|
|
|
|
# Get layout info
|
|
room_name, title = layout_map.get(device_id, (None, ''))
|
|
|
|
# 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']
|
|
|
|
device = Device(
|
|
device_id=device_id,
|
|
type=device_type,
|
|
name=dev_data.get('name', device_id),
|
|
friendly_name=title or dev_data.get('name', device_id),
|
|
room=room_name,
|
|
features=dev_data.get('features', {}),
|
|
read_only=read_only
|
|
)
|
|
devices.append(device)
|
|
|
|
logger.info(f"Loaded {len(devices)} devices from API")
|
|
return cls(devices)
|
|
|
|
def get_all(self) -> List[Device]:
|
|
"""Get all devices."""
|
|
return self._devices.copy()
|
|
|
|
def get_by_id(self, device_id: str) -> Optional[Device]:
|
|
"""
|
|
Get device by ID.
|
|
|
|
Args:
|
|
device_id: Device identifier
|
|
|
|
Returns:
|
|
Device or None if not found
|
|
"""
|
|
return self._by_id.get(device_id)
|
|
|
|
def get_by_type(self, device_type: str) -> List[Device]:
|
|
"""
|
|
Get all devices of a specific type.
|
|
|
|
Args:
|
|
device_type: Device type (e.g., "light", "thermostat")
|
|
|
|
Returns:
|
|
List of matching devices
|
|
"""
|
|
return [d for d in self._devices if d.type == device_type]
|
|
|
|
def get_by_room(self, room: str) -> List[Device]:
|
|
"""
|
|
Get all devices in a specific room.
|
|
|
|
Args:
|
|
room: Room name
|
|
|
|
Returns:
|
|
List of devices in the room
|
|
"""
|
|
return [d for d in self._devices if d.room == room]
|