diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4182eb22..24dab664 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.8 hooks: - id: ruff args: @@ -18,7 +18,7 @@ repos: exclude_types: [csv, json] exclude: ^tests/fixtures/|hahomematic/rega_scripts - repo: https://github.com/PyCQA/bandit - rev: 1.7.5 + rev: 1.7.6 hooks: - id: bandit args: diff --git a/changelog.md b/changelog.md index 56e0b687..32472eb3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +# Version 2023.12.2 (2023-12-15) + +- Save all rooms to entity model + # Version 2023.12.1 (2023-12-01) - Central name must not contain the identifier separator (@) diff --git a/hahomematic/caches/dynamic.py b/hahomematic/caches/dynamic.py index 23daab74..321d5514 100644 --- a/hahomematic/caches/dynamic.py +++ b/hahomematic/caches/dynamic.py @@ -23,7 +23,7 @@ InterfaceName, ) from hahomematic.platforms.device import HmDevice -from hahomematic.support import changed_within_seconds, get_device_address +from hahomematic.support import changed_within_seconds _LOGGER: Final = logging.getLogger(__name__) @@ -36,7 +36,6 @@ def __init__(self, central: hmcu.CentralUnit) -> None: self._central: Final = central self._channel_rooms: Final[dict[str, set[str]]] = {} self._device_channel_ids: Final[dict[str, str]] = {} - self._device_room: Final[dict[str, str]] = {} self._functions: Final[dict[str, set[str]]] = {} self._interface_cache: Final[dict[str, str]] = {} self._names_cache: Final[dict[str, str]] = {} @@ -53,7 +52,6 @@ async def load(self) -> None: _LOGGER.debug("load: Loading rooms for %s", self._central.name) self._channel_rooms.clear() self._channel_rooms.update(await self._get_all_rooms()) - self._identify_device_room() _LOGGER.debug("load: Loading functions for %s", self._central.name) self._functions.clear() self._functions.update(await self._get_all_functions()) @@ -92,9 +90,17 @@ async def _get_all_rooms(self) -> dict[str, set[str]]: return await client.get_all_rooms() return {} - def get_room(self, device_address: str) -> str | None: - """Return room by device_address.""" - return self._device_room.get(device_address) + def get_device_rooms(self, device_address: str) -> set[str]: + """Return all rooms by device_address.""" + rooms: set[str] = set() + for channel_address, channel_rooms in self._channel_rooms.items(): + if channel_address.startswith(device_address): + rooms.update(channel_rooms) + return rooms + + def get_channel_rooms(self, channel_address: str) -> set[str]: + """Return rooms by channel_address.""" + return self._channel_rooms.get(channel_address) or set() async def _get_all_functions(self) -> dict[str, set[str]]: """Get all functions, if available.""" @@ -123,21 +129,6 @@ def clear(self) -> None: self._functions.clear() self._last_refreshed = INIT_DATETIME - def _identify_device_room(self) -> None: - """ - Identify a possible room of a device. - - A room is relevant for a device, if there is only one room assigned to the channels. - """ - device_rooms: dict[str, set[str]] = {} - for address, rooms in self._channel_rooms.items(): - if (device_address := get_device_address(address=address)) not in device_rooms: - device_rooms[device_address] = set() - device_rooms[device_address].update(rooms) - for device_address, rooms in device_rooms.items(): - if rooms and len(set(rooms)) == 1: - self._device_room[device_address] = list(set(rooms))[0] - class CentralDataCache: """Central cache for device/channel initial data.""" diff --git a/hahomematic/central/decorators.py b/hahomematic/central/decorators.py index 11767b79..9fe4700a 100644 --- a/hahomematic/central/decorators.py +++ b/hahomematic/central/decorators.py @@ -25,7 +25,7 @@ def callback_system_event(system_event: SystemEvent) -> Callable: """Check if callback_system is set and call it AFTER original function.""" def decorator_callback_system_event( - func: Callable[_P, _R | Awaitable[_R]] + func: Callable[_P, _R | Awaitable[_R]], ) -> Callable[_P, _R | Awaitable[_R]]: """Decorate callback system events.""" diff --git a/hahomematic/exceptions.py b/hahomematic/exceptions.py index 82e961b3..9977612c 100644 --- a/hahomematic/exceptions.py +++ b/hahomematic/exceptions.py @@ -107,7 +107,7 @@ def log_exception( """Decorate methods for exception logging.""" def decorator_log_exception( - func: Callable[_P, _R | Awaitable[_R]] + func: Callable[_P, _R | Awaitable[_R]], ) -> Callable[_P, _R | Awaitable[_R]]: """Decorate log exception method.""" diff --git a/hahomematic/platforms/device.py b/hahomematic/platforms/device.py index a800f1dc..5ea9be30 100644 --- a/hahomematic/platforms/device.py +++ b/hahomematic/platforms/device.py @@ -104,7 +104,7 @@ def __init__(self, central: hmcu.CentralUnit, interface_id: str, device_address: device_type=self._device_type, ) self.value_cache: Final = ValueCache(device=self) - self._room: Final = central.device_details.get_room(device_address=device_address) + self._rooms: Final = central.device_details.get_device_rooms(device_address=device_address) self._update_firmware_data() self._update_entity: Final = ( HmUpdate(device=self) if self.device_type not in VIRTUAL_REMOTE_TYPES else None @@ -290,8 +290,15 @@ def product_group(self) -> ProductGroup: @config_property def room(self) -> str | None: - """Return the room of the device.""" - return self._room + """Return the room of the device, if only one assigned in CCU.""" + if self._rooms and len(self._rooms) == 1: + return list(self._rooms)[0] + return None + + @config_property + def rooms(self) -> set[str]: + """Return all rooms of the device.""" + return self._rooms @config_property def sub_type(self) -> str: diff --git a/hahomematic/platforms/entity.py b/hahomematic/platforms/entity.py index 3176e0bb..c0a8dca6 100644 --- a/hahomematic/platforms/entity.py +++ b/hahomematic/platforms/entity.py @@ -270,6 +270,9 @@ def __init__( self._channel_unique_id: Final = generate_channel_unique_id( central=device.central, address=self._channel_address ) + self._rooms: Final = self._central.device_details.get_channel_rooms( + channel_address=self._channel_address + ) self._is_in_multiple_channels: Final = is_in_multiple_channels self._channel_type: Final = str(device.channels[self._channel_address].type) self._function: Final = self._central.device_details.get_function_text( @@ -340,6 +343,18 @@ def name(self) -> str | None: """Return the name of the entity.""" return self._name + @config_property + def room(self) -> str | None: + """Return the room, if only one exists.""" + if self._rooms and len(self._rooms) == 1: + return list(self._rooms)[0] + return None + + @config_property + def rooms(self) -> set[str]: + """Return the rooms assigned to an entity.""" + return self._rooms + @config_property def usage(self) -> EntityUsage: """Return the entity usage.""" diff --git a/pyproject.toml b/pyproject.toml index fd0d376e..9ddb6d60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hahomematic" -version = "2023.12.1" +version = "2023.12.2" license = {text = "MIT License"} description = "Homematic interface for Home Assistant running on Python 3." readme = "README.md" diff --git a/requirements_test.txt b/requirements_test.txt index fb34e3aa..0a7e1e36 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,17 +1,17 @@ -r requirements.txt -r requirements_test_pre_commit.txt -coverage==7.3.2 -freezegun==1.2.2 +coverage==7.3.3 +freezegun==1.3.1 mypy==1.7.1 pip==23.3.1 -pre-commit==3.5.0 +pre-commit==3.6.0 pydevccu==0.1.7 pylint-per-file-ignores==1.3.2 pylint-strict-informational==0.1 -pylint==3.0.2 +pylint==3.0.3 pytest-aiohttp==1.0.5 -pytest-asyncio==0.21.1 +pytest-asyncio==0.23.2 pytest-cov==4.1.0 pytest-rerunfailures==13.0 pytest-socket==0.6.0 diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 4e1a7dbd..f411c96a 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,4 +1,4 @@ -bandit==1.7.5 +bandit==1.7.6 codespell==2.2.6 -ruff==0.1.6 +ruff==0.1.8 yamllint==1.33.0