From c9e4bd705d0646c0dcd093a3e1c5e436ffbba804 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Thu, 25 Jul 2024 15:32:20 +0200 Subject: [PATCH 1/7] Add button lock CE --- changelog.md | 1 + hahomematic/caches/visibility.py | 12 +++- hahomematic/const.py | 1 + hahomematic/platforms/custom/const.py | 2 + hahomematic/platforms/custom/definition.py | 8 +++ hahomematic/platforms/custom/lock.py | 81 +++++++++++++++++++--- hahomematic/platforms/entity.py | 70 ++++++++++--------- pyproject.toml | 2 +- tests/test_central.py | 2 - tests/test_central_pydevccu.py | 6 +- tests/test_entity.py | 2 +- tests/test_lock.py | 1 + 12 files changed, 139 insertions(+), 49 deletions(-) diff --git a/changelog.md b/changelog.md index 58376291..ca4444c9 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ - Rename last_updated -> modified_at - Rename last_refreshed -> refreshed_at +- Add button lock CE Add time units to HmIP-RGBW calls diff --git a/hahomematic/caches/visibility.py b/hahomematic/caches/visibility.py index 248fd3bf..b45c5bb4 100644 --- a/hahomematic/caches/visibility.py +++ b/hahomematic/caches/visibility.py @@ -52,7 +52,17 @@ "HmIP-HEATING": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), "HmIP-STH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), "HmIP-WTH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HmIP-eTRV": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), + "HmIP-eTRV": ( + ( + 0, + 1, + ), + ( + Parameter.TEMPERATURE_MAXIMUM, + Parameter.TEMPERATURE_MINIMUM, + Parameter.GLOBAL_BUTTON_LOCK, + ), + ), "HmIPW-STH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), "HmIPW-WTH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), } diff --git a/hahomematic/const.py b/hahomematic/const.py index eb2142cc..385967e5 100644 --- a/hahomematic/const.py +++ b/hahomematic/const.py @@ -250,6 +250,7 @@ class Parameter(StrEnum): ERROR = "ERROR" ERROR_JAMMED = "ERROR_JAMMED" FREQUENCY = "FREQUENCY" + GLOBAL_BUTTON_LOCK = "GLOBAL_BUTTON_LOCK" HEATING_COOLING = "HEATING_COOLING" HUE = "HUE" HUMIDITY = "HUMIDITY" diff --git a/hahomematic/platforms/custom/const.py b/hahomematic/platforms/custom/const.py index e24d329a..d183c64f 100644 --- a/hahomematic/platforms/custom/const.py +++ b/hahomematic/platforms/custom/const.py @@ -8,6 +8,7 @@ class DeviceProfile(StrEnum): """Enum for device profiles.""" + BUTTON_LOCK = "ButtonLock" IP_COVER = "IPCover" IP_DIMMER = "IPDimmer" IP_DRG_DALI = "IPDRGDALI" @@ -61,6 +62,7 @@ class Field(Enum): ACTIVE_PROFILE = "active_profile" AUTO_MODE = "auto_mode" BOOST_MODE = "boost_mode" + BUTTON_LOCK = "button_lock" CHANNEL_COLOR = "channel_color" CHANNEL_LEVEL = "channel_level" CHANNEL_LEVEL_2 = "channel_level_2" diff --git a/hahomematic/platforms/custom/definition.py b/hahomematic/platforms/custom/definition.py index fa79d7e1..7fb90ac4 100644 --- a/hahomematic/platforms/custom/definition.py +++ b/hahomematic/platforms/custom/definition.py @@ -279,6 +279,14 @@ }, }, }, + DeviceProfile.BUTTON_LOCK: { + ED.DEVICE_GROUP: { + ED.PRIMARY_CHANNEL: 0, + ED.REPEATABLE_FIELDS: { + Field.BUTTON_LOCK: Parameter.GLOBAL_BUTTON_LOCK, + }, + }, + }, DeviceProfile.IP_SIREN: { ED.DEVICE_GROUP: { ED.PRIMARY_CHANNEL: 3, diff --git a/hahomematic/platforms/custom/lock.py b/hahomematic/platforms/custom/lock.py index b4514944..8a8ec862 100644 --- a/hahomematic/platforms/custom/lock.py +++ b/hahomematic/platforms/custom/lock.py @@ -16,7 +16,7 @@ from hahomematic.platforms.custom.const import DeviceProfile, Field from hahomematic.platforms.custom.entity import CustomEntity from hahomematic.platforms.custom.support import CustomConfig, ExtendedConfig -from hahomematic.platforms.decorators import value_property +from hahomematic.platforms.decorators import config_property, value_property from hahomematic.platforms.entity import CallParameterCollector, bind_collector from hahomematic.platforms.generic.action import HmAction from hahomematic.platforms.generic.sensor import HmSensor @@ -65,19 +65,24 @@ def is_locked(self) -> bool: """Return true if lock is on.""" @value_property - @abstractmethod def is_jammed(self) -> bool: """Return true if lock is jammed.""" + return False @value_property - @abstractmethod def is_locking(self) -> bool | None: """Return true if the lock is locking.""" + return None @value_property - @abstractmethod def is_unlocking(self) -> bool | None: """Return true if the lock is unlocking.""" + return None + + @config_property + @abstractmethod + def supports_open(self) -> bool: + """Flag if lock supports open.""" @abstractmethod async def lock(self, collector: CallParameterCollector | None = None) -> None: @@ -127,10 +132,10 @@ def is_unlocking(self) -> bool | None: return str(self._e_direction.value) == LockActivity.UNLOCKING return None - @value_property - def is_jammed(self) -> bool: - """Return true if lock is jammed.""" - return False + @config_property + def supports_open(self) -> bool: + """Flag if lock supports open.""" + return True @bind_collector() async def lock(self, collector: CallParameterCollector | None = None) -> None: @@ -152,6 +157,42 @@ async def open(self, collector: CallParameterCollector | None = None) -> None: await self._e_lock_target_level.send_value(value=LockTargetLevel.OPEN, collector=collector) +class CeButtonLock(BaseLock): + """Class for HomematicIP button lock entities.""" + + def _init_entity_fields(self) -> None: + """Init the entity fields.""" + super()._init_entity_fields() + self._e_button_lock: HmSwitch = self._get_entity( + field=Field.BUTTON_LOCK, entity_type=HmSwitch + ) + + @value_property + def is_locked(self) -> bool: + """Return true if lock is on.""" + return self._e_button_lock.value is True + + @config_property + def supports_open(self) -> bool: + """Flag if lock supports open.""" + return False + + @bind_collector() + async def lock(self, collector: CallParameterCollector | None = None) -> None: + """Lock the lock.""" + await self._e_button_lock.turn_on(collector=collector) + + @bind_collector() + async def unlock(self, collector: CallParameterCollector | None = None) -> None: + """Unlock the lock.""" + await self._e_button_lock.turn_off(collector=collector) + + @bind_collector() + async def open(self, collector: CallParameterCollector | None = None) -> None: + """Open the lock.""" + return + + class CeRfLock(BaseLock): """Class for classic HomeMatic lock entities.""" @@ -191,6 +232,11 @@ def is_jammed(self) -> bool: """Return true if lock is jammed.""" return self._e_error.value is not None and self._e_error.value != LockError.NO_ERROR + @config_property + def supports_open(self) -> bool: + """Flag if lock supports open.""" + return True + @bind_collector() async def lock(self, collector: CallParameterCollector | None = None) -> None: """Lock the lock.""" @@ -222,6 +268,21 @@ def make_ip_lock( ) +def make_button_lock( + device: hmd.HmDevice, + group_base_channels: tuple[int, ...], + extended: ExtendedConfig | None = None, +) -> tuple[CustomEntity, ...]: + """Create HomematicIP lock entities.""" + return hmed.make_custom_entity( + device=device, + entity_class=CeButtonLock, + device_profile=DeviceProfile.BUTTON_LOCK, + group_base_channels=group_base_channels, + extended=extended, + ) + + def make_rf_lock( device: hmd.HmDevice, group_base_channels: tuple[int, ...], @@ -261,6 +322,10 @@ def make_rf_lock( } ), ), + "HmIP-eTRV": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), } hmed.ALL_DEVICES.append(DEVICES) diff --git a/hahomematic/platforms/entity.py b/hahomematic/platforms/entity.py index 021dc9ec..3953280a 100644 --- a/hahomematic/platforms/entity.py +++ b/hahomematic/platforms/entity.py @@ -36,7 +36,6 @@ Operations, Parameter, ParameterType, - ParamsetKey, ) from hahomematic.exceptions import HaHomematicException from hahomematic.platforms import device as hmd @@ -761,7 +760,7 @@ def __init__(self, device: hmd.HmDevice) -> None: """Init the generator.""" self._device: Final = device self._client: Final = device.client - self._paramsets: Final[dict[int, dict[str, dict[str, Any]]]] = {} + self._paramsets: Final[dict[str, dict[int, dict[str, dict[str, Any]]]]] = {} def add_entity( self, @@ -770,50 +769,55 @@ def add_entity( collector_order: int, ) -> None: """Add a generic entity.""" - if collector_order not in self._paramsets: - self._paramsets[collector_order] = {} - if entity.channel_address not in self._paramsets[collector_order]: - self._paramsets[collector_order][entity.channel_address] = {} - self._paramsets[collector_order][entity.channel_address][entity.parameter] = value + if entity.paramset_key not in self._paramsets: + self._paramsets[entity.paramset_key] = {} + if collector_order not in self._paramsets[entity.paramset_key]: + self._paramsets[entity.paramset_key][collector_order] = {} + if entity.channel_address not in self._paramsets[entity.paramset_key][collector_order]: + self._paramsets[entity.paramset_key][collector_order][entity.channel_address] = {} + self._paramsets[entity.paramset_key][collector_order][entity.channel_address][ + entity.parameter + ] = value async def send_data( self, wait_for_callback: int | None, use_command_queue: bool, use_put_paramset: bool ) -> bool: """Send data to backend.""" - for paramset_no in dict(sorted(self._paramsets.items())).values(): - for channel_address, paramset in paramset_no.items(): - if len(paramset.values()) == 1 or use_put_paramset is False: - for parameter, value in paramset.items(): - set_value_command = partial( - self._client.set_value, + for paramset_key, paramsets in self._paramsets.items(): # pylint: disable=too-many-nested-blocks + for paramset_no in dict(sorted(paramsets.items())).values(): + for channel_address, paramset in paramset_no.items(): + if use_put_paramset is False or len(paramset.values()) == 1: + for parameter, value in paramset.items(): + set_value_command = partial( + self._client.set_value, + channel_address=channel_address, + paramset_key=paramset_key, + parameter=parameter, + value=value, + wait_for_callback=wait_for_callback, + ) + if use_command_queue: + await self._device.central.command_queue_handler.put( + address=channel_address, + command=set_value_command, + ) + elif not await set_value_command(): + return False # pragma: no cover + else: + put_paramset_command = partial( + self._client.put_paramset, channel_address=channel_address, - paramset_key=ParamsetKey.VALUES, - parameter=parameter, - value=value, + paramset_key=paramset_key, + values=paramset, wait_for_callback=wait_for_callback, ) if use_command_queue: await self._device.central.command_queue_handler.put( address=channel_address, - command=set_value_command, + command=put_paramset_command, ) - elif not await set_value_command(): + elif not await put_paramset_command(): return False # pragma: no cover - else: - put_paramset_command = partial( - self._client.put_paramset, - channel_address=channel_address, - paramset_key=ParamsetKey.VALUES, - values=paramset, - wait_for_callback=wait_for_callback, - ) - if use_command_queue: - await self._device.central.command_queue_handler.put( - address=channel_address, - command=put_paramset_command, - ) - elif not await put_paramset_command(): - return False # pragma: no cover return True diff --git a/pyproject.toml b/pyproject.toml index afa7188c..da60d526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hahomematic" -version = "2024.6.0" +version = "2024.8.0" license = {text = "MIT License"} description = "Homematic interface for Home Assistant running on Python 3." readme = "README.md" diff --git a/tests/test_central.py b/tests/test_central.py index 64eb88f6..dd659324 100644 --- a/tests/test_central.py +++ b/tests/test_central.py @@ -148,8 +148,6 @@ async def test_identify_callback_ip( ("LEVEL@HmIP-eTRV-2", "LEVEL", 1, "VALUES", False), ("LEVEL@@HmIP-eTRV-2", "LEVEL", 1, "VALUES", False), ("HmIP-eTRV-2:1:MASTER", "LEVEL", 1, "VALUES", False), - ("GLOBAL_BUTTON_LOCK@HmIP-eTRV-2:0:MASTER", "GLOBAL_BUTTON_LOCK", 0, "MASTER", False), - ("GLOBAL_BUTTON_LOCK:MASTER@HmIP-eTRV-2:0", "GLOBAL_BUTTON_LOCK", 0, "MASTER", True), ("LEVEL:VALUES@all:all", "LEVEL", 1, "VALUES", True), ("LEVEL:VALUES@HmIP-eTRV-2:all", "LEVEL", 1, "VALUES", True), ("LEVEL:VALUES@all:1", "LEVEL", 1, "VALUES", True), diff --git a/tests/test_central_pydevccu.py b/tests/test_central_pydevccu.py index 59f9ab76..25ad6446 100644 --- a/tests/test_central_pydevccu.py +++ b/tests/test_central_pydevccu.py @@ -114,15 +114,15 @@ async def test_central_full(central_unit_full) -> None: ) as fptr: fptr.write(orjson.dumps(addresses, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS)) - assert usage_types[EntityUsage.NO_CREATE] == 3110 - assert usage_types[EntityUsage.CE_PRIMARY] == 194 + assert usage_types[EntityUsage.NO_CREATE] == 3116 + assert usage_types[EntityUsage.CE_PRIMARY] == 200 assert usage_types[EntityUsage.ENTITY] == 3799 assert usage_types[EntityUsage.CE_VISIBLE] == 114 assert usage_types[EntityUsage.CE_SECONDARY] == 146 assert len(ce_channels) == 118 assert len(entity_types) == 6 - assert len(parameters) == 215 + assert len(parameters) == 216 assert len(central_unit_full._devices) == 383 virtual_remotes = ["VCU4264293", "VCU0000057", "VCU0000001"] diff --git a/tests/test_entity.py b/tests/test_entity.py index 81ff45e2..9c6c29ab 100644 --- a/tests/test_entity.py +++ b/tests/test_entity.py @@ -229,5 +229,5 @@ async def test_generic_wrapped_entity( def test_custom_required_entities() -> None: """Test required parameters from entity definitions.""" required_parameters = get_required_parameters() - assert len(required_parameters) == 75 + assert len(required_parameters) == 76 assert check_ignore_parameters_is_clean() is True diff --git a/tests/test_lock.py b/tests/test_lock.py index 8b13451e..0b0d3c75 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -18,6 +18,7 @@ TEST_DEVICES: dict[str, str] = { "VCU9724704": "HmIP-DLD.json", "VCU0000146": "HM-Sec-Key.json", + "VCU3609622": "HmIP-eTRV-2.json", } # pylint: disable=protected-access From 4a79dfc179bf610ceebb4442342fbe2385be2eca Mon Sep 17 00:00:00 2001 From: SukramJ Date: Thu, 25 Jul 2024 16:05:20 +0200 Subject: [PATCH 2/7] Add more devices to button lock --- hahomematic/caches/visibility.py | 47 +++++++++++++++++++--------- hahomematic/platforms/custom/lock.py | 24 ++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/hahomematic/caches/visibility.py b/hahomematic/caches/visibility.py index b45c5bb4..e22ece1d 100644 --- a/hahomematic/caches/visibility.py +++ b/hahomematic/caches/visibility.py @@ -27,6 +27,24 @@ _RELEVANT_MASTER_PARAMSETS_BY_DEVICE: Final[ Mapping[str, tuple[tuple[int, ...], tuple[Parameter, ...]]] ] = { + "ALPHA-IP-RBG": ( + (0, 1), + ( + Parameter.TEMPERATURE_MAXIMUM, + Parameter.TEMPERATURE_MINIMUM, + Parameter.GLOBAL_BUTTON_LOCK, + ), + ), + "HM-CC-RT-DN": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), + "HM-CC-VG-1": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), + "HmIP-BWTH": ( + (0, 1), + ( + Parameter.TEMPERATURE_MAXIMUM, + Parameter.TEMPERATURE_MINIMUM, + Parameter.GLOBAL_BUTTON_LOCK, + ), + ), "HmIP-DRBLI4": ( (1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 17, 21), (Parameter.CHANNEL_OPERATION_MODE,), @@ -35,36 +53,37 @@ "HmIP-DRSI1": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIP-DRSI4": ((1, 2, 3, 4), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIP-DSD-PCB": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)), + "HmIP-FAL": ((0,), (Parameter.GLOBAL_BUTTON_LOCK,)), "HmIP-FCI1": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIP-FCI6": (tuple(range(1, 7)), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIP-FSI16": ((1,), (Parameter.CHANNEL_OPERATION_MODE,)), + "HmIP-HEATING": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), "HmIP-MIO16-PCB": ((13, 14, 15, 16), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIP-MOD-RC8": (tuple(range(1, 9)), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIP-RGBW": ((0,), (Parameter.DEVICE_OPERATION_MODE,)), - "HmIPW-DRBL4": ((1, 5, 9, 13), (Parameter.CHANNEL_OPERATION_MODE,)), - "HmIPW-DRI16": (tuple(range(1, 17)), (Parameter.CHANNEL_OPERATION_MODE,)), - "HmIPW-DRI32": (tuple(range(1, 33)), (Parameter.CHANNEL_OPERATION_MODE,)), - "HmIPW-FIO6": (tuple(range(1, 7)), (Parameter.CHANNEL_OPERATION_MODE,)), - "ALPHA-IP-RBG": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HM-CC-RT-DN": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HM-CC-VG-1": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HmIP-BWTH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HmIP-HEATING": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), "HmIP-STH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HmIP-WTH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HmIP-eTRV": ( + "HmIP-WTH": ( + (0, 1), ( - 0, - 1, + Parameter.TEMPERATURE_MAXIMUM, + Parameter.TEMPERATURE_MINIMUM, + Parameter.GLOBAL_BUTTON_LOCK, ), + ), + "HmIP-eTRV": ( + (0, 1), ( Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM, Parameter.GLOBAL_BUTTON_LOCK, ), ), + "HmIPW-DRBL4": ((1, 5, 9, 13), (Parameter.CHANNEL_OPERATION_MODE,)), + "HmIPW-DRI16": (tuple(range(1, 17)), (Parameter.CHANNEL_OPERATION_MODE,)), + "HmIPW-DRI32": (tuple(range(1, 33)), (Parameter.CHANNEL_OPERATION_MODE,)), + "HmIPW-FAL": ((0,), (Parameter.GLOBAL_BUTTON_LOCK,)), + "HmIPW-FIO6": (tuple(range(1, 7)), (Parameter.CHANNEL_OPERATION_MODE,)), "HmIPW-STH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), - "HmIPW-WTH": ((1,), (Parameter.TEMPERATURE_MAXIMUM, Parameter.TEMPERATURE_MINIMUM)), } # Some parameters are marked as INTERNAL in the paramset and not considered by default, diff --git a/hahomematic/platforms/custom/lock.py b/hahomematic/platforms/custom/lock.py index 8a8ec862..7acd5773 100644 --- a/hahomematic/platforms/custom/lock.py +++ b/hahomematic/platforms/custom/lock.py @@ -322,10 +322,34 @@ def make_rf_lock( } ), ), + "ALPHA-IP-RBG": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), + "HmIP-BWTH": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), + "HmIP-FAL": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), + "HmIP-WTH": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), "HmIP-eTRV": CustomConfig( make_ce_func=make_button_lock, channels=(0,), ), + "HmIPW-FAL": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), + "HmIPW-WTH": CustomConfig( + make_ce_func=make_button_lock, + channels=(0,), + ), } hmed.ALL_DEVICES.append(DEVICES) From 440a2c9348aa2ad7025d665853f0c63ac93b7f9b Mon Sep 17 00:00:00 2001 From: SukramJ Date: Thu, 25 Jul 2024 17:26:42 +0200 Subject: [PATCH 3/7] determine is_multi_channel_device by platform --- hahomematic/platforms/custom/climate.py | 2 +- hahomematic/platforms/custom/cover.py | 2 +- hahomematic/platforms/custom/definition.py | 15 +++++++++------ hahomematic/platforms/custom/entity.py | 4 +++- hahomematic/platforms/custom/light.py | 2 +- hahomematic/platforms/custom/lock.py | 2 +- hahomematic/platforms/custom/siren.py | 2 +- hahomematic/platforms/custom/switch.py | 2 +- 8 files changed, 18 insertions(+), 13 deletions(-) diff --git a/hahomematic/platforms/custom/climate.py b/hahomematic/platforms/custom/climate.py index bb7137c7..97dee9e8 100644 --- a/hahomematic/platforms/custom/climate.py +++ b/hahomematic/platforms/custom/climate.py @@ -699,6 +699,6 @@ def make_ip_thermostat_group( "Thermostat AA": CustomConfig(make_ce_func=make_ip_thermostat, channels=(1,)), "ZEL STG RM FWT": CustomConfig(make_ce_func=make_simple_thermostat, channels=(1,)), } -hmed.ALL_DEVICES.append(DEVICES) +hmed.ALL_DEVICES[HmPlatform.CLIMATE] = DEVICES BLACKLISTED_DEVICES: tuple[str, ...] = ("HmIP-STHO",) hmed.ALL_BLACKLISTED_DEVICES.append(BLACKLISTED_DEVICES) diff --git a/hahomematic/platforms/custom/cover.py b/hahomematic/platforms/custom/cover.py index 19ed3792..f7c3720f 100644 --- a/hahomematic/platforms/custom/cover.py +++ b/hahomematic/platforms/custom/cover.py @@ -742,4 +742,4 @@ def make_rf_window_drive( ), "ZEL STG RM FEP 230V": CustomConfig(make_ce_func=make_rf_cover, channels=(1,)), } -hmed.ALL_DEVICES.append(DEVICES) +hmed.ALL_DEVICES[HmPlatform.COVER] = DEVICES diff --git a/hahomematic/platforms/custom/definition.py b/hahomematic/platforms/custom/definition.py index 7fb90ac4..6e80d82d 100644 --- a/hahomematic/platforms/custom/definition.py +++ b/hahomematic/platforms/custom/definition.py @@ -10,7 +10,7 @@ import voluptuous as vol from hahomematic import support as hms -from hahomematic.const import Parameter +from hahomematic.const import HmPlatform, Parameter from hahomematic.platforms import device as hmd from hahomematic.platforms.custom import entity as hmce from hahomematic.platforms.custom.const import ED, DeviceProfile, Field @@ -21,7 +21,7 @@ DEFAULT_INCLUDE_DEFAULT_ENTITIES: Final = True -ALL_DEVICES: list[Mapping[str, CustomConfig | tuple[CustomConfig, ...]]] = [] +ALL_DEVICES: dict[HmPlatform, Mapping[str, CustomConfig | tuple[CustomConfig, ...]]] = {} ALL_BLACKLISTED_DEVICES: list[tuple[str, ...]] = [] SCHEMA_ED_ADDITIONAL_ENTITIES = vol.Schema( @@ -696,6 +696,7 @@ def _get_device_entities( def get_entity_configs( device_type: str, + platform: HmPlatform | None = None, ) -> tuple[CustomConfig | tuple[CustomConfig, ...], ...]: """Return the entity configs to create custom entities.""" device_type = device_type.lower().replace("hb-", "hm-") @@ -707,7 +708,9 @@ def get_entity_configs( ): return () - for platform_devices in ALL_DEVICES: + for pf, platform_devices in ALL_DEVICES.items(): + if platform is not None and pf != platform: + continue if func := _get_entity_config_by_platform( platform_devices=platform_devices, device_type=device_type, @@ -732,10 +735,10 @@ def _get_entity_config_by_platform( return None -def is_multi_channel_device(device_type: str) -> bool: +def is_multi_channel_device(device_type: str, platform: HmPlatform) -> bool: """Return true, if device has multiple channels.""" channels: list[int] = [] - for entity_configs in get_entity_configs(device_type=device_type): + for entity_configs in get_entity_configs(device_type=device_type, platform=platform): if isinstance(entity_configs, CustomConfig): channels.extend(entity_configs.channels) else: @@ -767,7 +770,7 @@ def get_required_parameters() -> tuple[Parameter, ...]: ): required_parameters.extend(additional_entities) - for platform_spec in ALL_DEVICES: + for platform_spec in ALL_DEVICES.values(): for custom_configs in platform_spec.values(): if isinstance(custom_configs, CustomConfig): if extended := custom_configs.extended: diff --git a/hahomematic/platforms/custom/entity.py b/hahomematic/platforms/custom/entity.py index c2665c8c..79b4bee4 100644 --- a/hahomematic/platforms/custom/entity.py +++ b/hahomematic/platforms/custom/entity.py @@ -50,7 +50,9 @@ def __init__( device=device, unique_id=unique_id, channel_no=channel_no, - is_in_multiple_channels=hmed.is_multi_channel_device(device_type=device.device_type), + is_in_multiple_channels=hmed.is_multi_channel_device( + device_type=device.device_type, platform=self.platform + ), ) self._extended: Final = extended self._data_entities: Final[dict[Field, hmge.GenericEntity]] = {} diff --git a/hahomematic/platforms/custom/light.py b/hahomematic/platforms/custom/light.py index 2f2a320e..0e1d1218 100644 --- a/hahomematic/platforms/custom/light.py +++ b/hahomematic/platforms/custom/light.py @@ -1160,4 +1160,4 @@ def make_ip_drg_dali_light( ), "OLIGO.smart.iq.HM": CustomConfig(make_ce_func=make_rf_dimmer, channels=(1, 2, 3, 4, 5, 6)), } -hmed.ALL_DEVICES.append(DEVICES) +hmed.ALL_DEVICES[HmPlatform.LIGHT] = DEVICES diff --git a/hahomematic/platforms/custom/lock.py b/hahomematic/platforms/custom/lock.py index 7acd5773..5c543877 100644 --- a/hahomematic/platforms/custom/lock.py +++ b/hahomematic/platforms/custom/lock.py @@ -352,4 +352,4 @@ def make_rf_lock( ), } -hmed.ALL_DEVICES.append(DEVICES) +hmed.ALL_DEVICES[HmPlatform.LOCK] = DEVICES diff --git a/hahomematic/platforms/custom/siren.py b/hahomematic/platforms/custom/siren.py index a94038c2..46c70a1a 100644 --- a/hahomematic/platforms/custom/siren.py +++ b/hahomematic/platforms/custom/siren.py @@ -270,4 +270,4 @@ def make_ip_siren_smoke( "HmIP-ASIR": CustomConfig(make_ce_func=make_ip_siren, channels=(0,)), "HmIP-SWSD": CustomConfig(make_ce_func=make_ip_siren_smoke, channels=(0,)), } -hmed.ALL_DEVICES.append(DEVICES) +hmed.ALL_DEVICES[HmPlatform.SIREN] = DEVICES diff --git a/hahomematic/platforms/custom/switch.py b/hahomematic/platforms/custom/switch.py index d6c1a49a..aa30417b 100644 --- a/hahomematic/platforms/custom/switch.py +++ b/hahomematic/platforms/custom/switch.py @@ -155,4 +155,4 @@ def make_ip_switch( ), "HmIPW-FIO6": CustomConfig(make_ce_func=make_ip_switch, channels=(7, 11, 15, 19, 23, 27)), } -hmed.ALL_DEVICES.append(DEVICES) +hmed.ALL_DEVICES[HmPlatform.SWITCH] = DEVICES From f6ff758a561905fb6ceef0580765f5b9b6ac5feb Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 26 Jul 2024 10:17:51 +0200 Subject: [PATCH 4/7] Add ce name postfix --- hahomematic/platforms/custom/entity.py | 6 ++++++ hahomematic/platforms/custom/lock.py | 5 +++++ hahomematic/platforms/support.py | 7 ++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/hahomematic/platforms/custom/entity.py b/hahomematic/platforms/custom/entity.py index 79b4bee4..d3eb3fe0 100644 --- a/hahomematic/platforms/custom/entity.py +++ b/hahomematic/platforms/custom/entity.py @@ -122,6 +122,11 @@ def _relevant_entities(self) -> tuple[hmge.GenericEntity, ...]: """Returns the list of relevant entities. To be overridden by subclasses.""" return self._readable_entities + @property + def entity_name_postfix(self) -> str: + """Return the entity name postfix.""" + return "" + def _get_entity_name(self) -> EntityNameData: """Create the name for the entity.""" is_only_primary_channel = check_channel_is_the_only_primary_channel( @@ -135,6 +140,7 @@ def _get_entity_name(self) -> EntityNameData: channel_no=self.channel_no, is_only_primary_channel=is_only_primary_channel, usage=self._usage, + postfix=self.entity_name_postfix.replace("_", " ").title(), ) def _get_entity_usage(self) -> EntityUsage: diff --git a/hahomematic/platforms/custom/lock.py b/hahomematic/platforms/custom/lock.py index 5c543877..298caa51 100644 --- a/hahomematic/platforms/custom/lock.py +++ b/hahomematic/platforms/custom/lock.py @@ -167,6 +167,11 @@ def _init_entity_fields(self) -> None: field=Field.BUTTON_LOCK, entity_type=HmSwitch ) + @property + def entity_name_postfix(self) -> str: + """Return the entity name postfix.""" + return "BUTTON_LOCK" + @value_property def is_locked(self) -> bool: """Return true if lock is on.""" diff --git a/hahomematic/platforms/support.py b/hahomematic/platforms/support.py index f4792b86..5ce33904 100644 --- a/hahomematic/platforms/support.py +++ b/hahomematic/platforms/support.py @@ -248,6 +248,7 @@ def get_custom_entity_name( channel_no: int | None, is_only_primary_channel: bool, usage: EntityUsage, + postfix: str = "", ) -> EntityNameData: """Get name for custom entity.""" if channel_name := _get_base_name_from_channel_or_device( @@ -256,7 +257,11 @@ def get_custom_entity_name( channel_no=channel_no, ): if is_only_primary_channel and _check_channel_name_with_channel_no(name=channel_name): - return EntityNameData(device_name=device.name, channel_name=channel_name.split(":")[0]) + return EntityNameData( + device_name=device.name, + channel_name=channel_name.split(":")[0], + parameter_name=postfix, + ) if _check_channel_name_with_channel_no(name=channel_name): c_name = channel_name.split(":")[0] p_name = channel_name.split(":")[1] From b3258350ae96e032baab5ccb1634275d9b852264 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 26 Jul 2024 10:43:13 +0200 Subject: [PATCH 5/7] update version --- .pre-commit-config.yaml | 2 +- changelog.md | 2 +- pyproject.toml | 2 +- requirements_test.txt | 2 +- requirements_test_pre_commit.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75cc4964..726df616 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.5.4 + rev: v0.5.5 hooks: - id: ruff args: diff --git a/changelog.md b/changelog.md index ca4444c9..e8c32e6c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Version 2024.8.0 (2024-08-01) +# Version 2024.7.0 (2024-07-26) - Rename last_updated -> modified_at - Rename last_refreshed -> refreshed_at diff --git a/pyproject.toml b/pyproject.toml index da60d526..b98c7c21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hahomematic" -version = "2024.8.0" +version = "2024.7.0" 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 7b859fc9..38cea870 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -17,5 +17,5 @@ pytest-cov==5.0.0 pytest-rerunfailures==14.0 pytest-socket==0.7.0 pytest-timeout==2.3.1 -pytest==8.3.1 +pytest==8.3.2 types-python-slugify==8.0.2.20240310 diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index c36df7f8..c7f6c088 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,4 +1,4 @@ bandit==1.7.9 codespell==2.3.0 -ruff==0.5.4 +ruff==0.5.5 yamllint==1.35.1 From 599f57ae7c1b3b588d4cc6ac93256092533ea12e Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 26 Jul 2024 10:47:01 +0200 Subject: [PATCH 6/7] Update test_central_pydevccu.py --- tests/test_central_pydevccu.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_central_pydevccu.py b/tests/test_central_pydevccu.py index 25ad6446..c1a62043 100644 --- a/tests/test_central_pydevccu.py +++ b/tests/test_central_pydevccu.py @@ -114,13 +114,13 @@ async def test_central_full(central_unit_full) -> None: ) as fptr: fptr.write(orjson.dumps(addresses, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS)) - assert usage_types[EntityUsage.NO_CREATE] == 3116 - assert usage_types[EntityUsage.CE_PRIMARY] == 200 - assert usage_types[EntityUsage.ENTITY] == 3799 + assert usage_types[EntityUsage.NO_CREATE] == 3213 + assert usage_types[EntityUsage.CE_PRIMARY] == 207 + assert usage_types[EntityUsage.ENTITY] == 3707 assert usage_types[EntityUsage.CE_VISIBLE] == 114 assert usage_types[EntityUsage.CE_SECONDARY] == 146 - assert len(ce_channels) == 118 + assert len(ce_channels) == 121 assert len(entity_types) == 6 assert len(parameters) == 216 From b952390556e6fef50fb8a9efc2ed99a58d69da0b Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 26 Jul 2024 10:51:25 +0200 Subject: [PATCH 7/7] Update test_central_pydevccu.py --- tests/test_central_pydevccu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_central_pydevccu.py b/tests/test_central_pydevccu.py index c1a62043..70401e71 100644 --- a/tests/test_central_pydevccu.py +++ b/tests/test_central_pydevccu.py @@ -28,7 +28,7 @@ async def test_central_mini(central_unit_mini) -> None: assert central_unit_mini.get_client(const.INTERFACE_ID).model == "PyDevCCU" assert central_unit_mini.primary_client.model == "PyDevCCU" assert len(central_unit_mini._devices) == 1 - assert len(central_unit_mini.get_entities(exclude_no_create=False)) == 29 + assert len(central_unit_mini.get_entities(exclude_no_create=False)) == 31 @pytest.mark.asyncio()