"""Controller holding and managing HUE resources that are of the config type.""" from typing import TYPE_CHECKING from awesomeversion import AwesomeVersion from aiohue.errors import BridgeSoftwareOutdated from aiohue.util import mac_from_bridge_id from aiohue.v2.models.behavior_instance import BehaviorInstance, BehaviorInstancePut from aiohue.v2.models.behavior_script import BehaviorScript from aiohue.v2.models.bridge import Bridge from aiohue.v2.models.bridge_home import BridgeHome from aiohue.v2.models.convenience_area_motion import ConvenienceAreaMotion from aiohue.v2.models.device import Device from aiohue.v2.models.entertainment import Entertainment from aiohue.v2.models.entertainment_configuration import EntertainmentConfiguration from aiohue.v2.models.homekit import Homekit from aiohue.v2.models.matter import Matter from aiohue.v2.models.matter_fabric import MatterFabric from aiohue.v2.models.motion_area_configuration import ( MotionAreaConfiguration, MotionAreaConfigurationPut, ) from aiohue.v2.models.resource import ResourceTypes from aiohue.v2.models.room import Room from aiohue.v2.models.security_area_motion import SecurityAreaMotion from aiohue.v2.models.service_group import ( ServiceGroup, ServiceGroupMetadata, ServiceGroupPut, ) from aiohue.v2.models.zone import Zone from .base import BaseResourcesController, GroupedControllerBase if TYPE_CHECKING: from aiohue.v2 import HueBridgeV2 class BridgeController(BaseResourcesController[type[Bridge]]): """Controller holding and managing HUE resources of type `bridge`.""" item_type = ResourceTypes.BRIDGE item_cls = Bridge @property def id(self) -> str | None: """Return id of the only/first bridge found in items.""" for item in self.items: return item.id return None class BridgeHomeController(BaseResourcesController[type[BridgeHome]]): """Controller holding and managing HUE resources of type `bridge_home`.""" item_type = ResourceTypes.BRIDGE_HOME item_cls = BridgeHome class EntertainmentController(BaseResourcesController[type[Entertainment]]): """Controller holding and managing HUE resources of type `entertainment`.""" item_type = ResourceTypes.ENTERTAINMENT item_cls = Entertainment allow_parser_error = True class EntertainmentConfigurationController( BaseResourcesController[type[EntertainmentConfiguration]] ): """Controller holding and managing HUE resources of type `entertainment_configuration`.""" item_type = ResourceTypes.ENTERTAINMENT_CONFIGURATION item_cls = EntertainmentConfiguration allow_parser_error = True class HomekitController(BaseResourcesController[type[Homekit]]): """Controller holding and managing HUE resources of type `homekit`.""" item_type = ResourceTypes.HOMEKIT item_cls = Homekit allow_parser_error = True class MatterController(BaseResourcesController[type[Matter]]): """Controller holding and managing HUE resources of type `matter`.""" item_type = ResourceTypes.MATTER item_cls = Matter allow_parser_error = True class MatterFabricController(BaseResourcesController[type[MatterFabric]]): """Controller holding and managing HUE resources of type `matter_fabric`.""" item_type = ResourceTypes.MATTER_FABRIC item_cls = MatterFabric allow_parser_error = True class BehaviorScriptController(BaseResourcesController[type[BehaviorScript]]): """Controller holding and managing HUE resources of type `behavior_script`.""" item_type = ResourceTypes.BEHAVIOR_SCRIPT item_cls = BehaviorScript allow_parser_error = True class BehaviorInstanceController(BaseResourcesController[type[BehaviorInstance]]): """Controller holding and managing HUE resources of type `behavior_instance`.""" item_type = ResourceTypes.BEHAVIOR_INSTANCE item_cls = BehaviorInstance allow_parser_error = True async def set_enabled(self, id: str, enabled: bool) -> None: """Enable/Disable sensor.""" await self.update(id, BehaviorInstancePut(enabled=enabled)) class MotionAreaConfigurationController( BaseResourcesController[type[MotionAreaConfiguration]] ): """Controller holding and managing HUE resources of type `motion_area_configuration`.""" item_type = ResourceTypes.MOTION_AREA_CONFIGURATION item_cls = MotionAreaConfiguration allow_parser_error = True def get_sensors(self, id: str) -> list[ConvenienceAreaMotion | SecurityAreaMotion]: """Return all sensors in given motion area configuration.""" return [ x for x in self._bridge.sensors if x.owner.rid == id and isinstance(x, (ConvenienceAreaMotion, SecurityAreaMotion)) ] def get_group(self, id: str) -> Room | Zone: """Return the group associated with this motion area configuration.""" if id not in self._items: raise KeyError(f"Motion area configuration {id} not found") group_id = self._items[id].group.rid if group_id not in self._bridge.groups: raise KeyError(f"Group {group_id} not found") return self._bridge.groups[group_id] async def set_enabled(self, id: str, enabled: bool) -> None: """Enable/Disable motion area configuration.""" await self.update(id, MotionAreaConfigurationPut(enabled=enabled)) async def set_name(self, id: str, name: str) -> None: """Set name of motion area configuration.""" await self.update(id, MotionAreaConfigurationPut(name=name)) class ServiceGroupController(BaseResourcesController[type[ServiceGroup]]): """Controller holding and managing HUE resources of type `service_group`.""" item_type = ResourceTypes.SERVICE_GROUP item_cls = ServiceGroup allow_parser_error = True async def set_name(self, id: str, name: str) -> None: """Set name of service group.""" await self.update(id, ServiceGroupPut(metadata=ServiceGroupMetadata(name=name))) class ConfigController( GroupedControllerBase[ Bridge | BridgeHome | Entertainment | EntertainmentConfiguration | MotionAreaConfiguration | ServiceGroup ] ): """ Controller holding and managing HUE resources there are of the config type. Note that the properties will raise AttributeError if not connected to a bridge. """ @property def bridge_id(self) -> str: """Return bridge_id of bridge we're connected to.""" return self.bridge.bridge_id @property def name(self) -> str: """Return name of bridge we're connected to.""" return self.bridge_device.metadata.name @property def mac_address(self) -> str: """Return mac address of bridge we're connected to.""" # the network mac is not available in api so we parse it from the id return mac_from_bridge_id(self.bridge_id) @property def model_id(self) -> str: """Return model ID of bridge we're connected to.""" return self.bridge_device.product_data.model_id @property def software_version(self) -> str: """Return software version of bridge we're connected to.""" return self.bridge_device.product_data.software_version @property def bridge(self) -> Bridge: """Return the only/first bridge found in items of resource `bridge`.""" # the Hue resource system in V2 is generic and even the bridge object is returned as array # there should be only one object returned here return next(item for item in self.bridges) @property def bridge_device(self) -> Device: """Return the device object belonging to the bridge.""" # the Hue resource system in V2 is generic and even the bridge metadata # can (only) be retrieved as a device object # do the plumbing by looking it up here. # this device object contains the metadata that is more or like comparable # with the V1 output like it's name and software version. if bridge := self.bridge: for device in self._bridge.devices: for service in device.services: if service.rid == bridge.id: return device raise AttributeError("bridge_device") def check_version(self, version: str) -> bool: """Check if bridge version is equal to (or higher than) given version.""" current = AwesomeVersion(self.software_version) required = AwesomeVersion(version) return current >= required def require_version(self, version: str) -> None: """Raise exception if Bridge version is lower than given minimal version.""" if not self.check_version(version): raise BridgeSoftwareOutdated( f"Bridge software version outdated. Minimal required version is {version}" ) def __init__(self, bridge: "HueBridgeV2") -> None: """Initialize underlying controller instances.""" self.bridges = BridgeController(bridge) self.bridge_home = BridgeHomeController(bridge) self.entertainment = EntertainmentController(bridge) self.entertainment_configuration = EntertainmentConfigurationController(bridge) self.homekit = HomekitController(bridge) self.matter = MatterController(bridge) self.matter_fabric = MatterFabricController(bridge) self.behavior_script = BehaviorScriptController(bridge) self.behavior_instance = BehaviorInstanceController(bridge) self.motion_area_configuration = MotionAreaConfigurationController(bridge) self.service_group = ServiceGroupController(bridge) super().__init__( bridge, [ self.bridges, self.bridge_home, self.entertainment, self.entertainment_configuration, self.homekit, self.matter, self.matter_fabric, self.behavior_script, self.behavior_instance, self.motion_area_configuration, self.service_group, ], )