From 04841e026f6774901e49fee2525ff8ae681ddf3e Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 5 Jan 2024 14:24:58 +0100 Subject: [PATCH] Allow ordered execution of collector paramsets (#1356) --- changelog.md | 4 ++ hahomematic/platforms/custom/light.py | 6 ++- hahomematic/platforms/entity.py | 54 ++++++++++++++----------- hahomematic/platforms/generic/entity.py | 3 +- pyproject.toml | 2 +- tests/test_light.py | 17 +++++++- 6 files changed, 57 insertions(+), 29 deletions(-) diff --git a/changelog.md b/changelog.md index c40eea2c..40730c46 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +# Version 2024.1.1 (2024-01-05) + +- Allow ordered execution of collector paramsets + # Version 2024.1.0 (2024-01-03) - Add duration=0 when ramp_time used for HmIP-RGBW diff --git a/hahomematic/platforms/custom/light.py b/hahomematic/platforms/custom/light.py index 771cccc1..b6bb190e 100644 --- a/hahomematic/platforms/custom/light.py +++ b/hahomematic/platforms/custom/light.py @@ -386,11 +386,13 @@ async def turn_on( return if "effect" not in kwargs and self.supports_effects and self.effect != _EFFECT_OFF: - await self._e_effect.send_value(value=0, collector=collector) + await self._e_effect.send_value(value=0, collector=collector, collector_order=5) if self.supports_effects and (effect := kwargs.get("effect")) is not None: if (effect_idx := self._effects.index(effect)) is not None: - await self._e_effect.send_value(value=effect_idx, collector=collector) + await self._e_effect.send_value( + value=effect_idx, collector=collector, collector_order=95 + ) await super().turn_on(collector=collector, **kwargs) diff --git a/hahomematic/platforms/entity.py b/hahomematic/platforms/entity.py index c0a8dca6..1cd64987 100644 --- a/hahomematic/platforms/entity.py +++ b/hahomematic/platforms/entity.py @@ -51,6 +51,8 @@ "KEY_TRANSCEIVER", "MULTI_MODE_INPUT_TRANSMITTER", ) +_COLLECTOR_ARGUMENT_NAME = "collector" + DEFAULT_CUSTOM_ID: Final = "custom_id" _FIX_UNIT_REPLACE: Final[Mapping[str, str]] = { @@ -729,41 +731,47 @@ def __init__(self, client: hmcl.Client) -> None: """Init the generator.""" self._client: Final = client self._use_put_paramset: bool = True - self._paramsets: Final[dict[str, dict[str, Any]]] = {} + self._paramsets: Final[dict[int, dict[str, dict[str, Any]]]] = {} def add_entity( - self, entity: BaseParameterEntity, value: Any, use_put_paramset: bool = True + self, + entity: BaseParameterEntity, + value: Any, + collector_order: int, + use_put_paramset: bool = True, ) -> None: """Add a generic entity.""" if use_put_paramset is False: self._use_put_paramset = False - if entity.channel_address not in self._paramsets: - self._paramsets[entity.channel_address] = {} - self._paramsets[entity.channel_address][entity.parameter] = value + 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 async def send_data(self) -> bool: """Send data to backend.""" - for channel_address, paramset in self._paramsets.items(): - if len(paramset.values()) == 1 or self._use_put_paramset is False: - for parameter, value in paramset.items(): - if not await self._client.set_value( - channel_address=channel_address, - paramset_key=ParamsetKey.VALUES, - parameter=parameter, - value=value, - ): - return False # pragma: no cover - elif not await self._client.put_paramset( - address=channel_address, paramset_key=ParamsetKey.VALUES, value=paramset - ): - return False # pragma: no cover + for paramset_no in dict(sorted(self._paramsets.items())).values(): + for channel_address, paramset in paramset_no.items(): + if len(paramset.values()) == 1 or self._use_put_paramset is False: + for parameter, value in paramset.items(): + if not await self._client.set_value( + channel_address=channel_address, + paramset_key=ParamsetKey.VALUES, + parameter=parameter, + value=value, + ): + return False # pragma: no cover + elif not await self._client.put_paramset( + address=channel_address, paramset_key=ParamsetKey.VALUES, value=paramset + ): + return False # pragma: no cover return True def bind_collector(func: _CallableT) -> _CallableT: """Decorate function to automatically add collector if not set.""" - argument_name = "collector" - argument_index = getfullargspec(func).args.index(argument_name) + argument_index = getfullargspec(func).args.index(_COLLECTOR_ARGUMENT_NAME) @wraps(func) async def wrapper_collector(*args: Any, **kwargs: Any) -> Any: @@ -771,13 +779,13 @@ async def wrapper_collector(*args: Any, **kwargs: Any) -> Any: try: collector_exists = args[argument_index] is not None except IndexError: - collector_exists = kwargs.get(argument_name) is not None + collector_exists = kwargs.get(_COLLECTOR_ARGUMENT_NAME) is not None if collector_exists: return_value = await func(*args, **kwargs) else: collector = CallParameterCollector(client=args[0].device.client) - kwargs[argument_name] = collector + kwargs[_COLLECTOR_ARGUMENT_NAME] = collector return_value = await func(*args, **kwargs) await collector.send_data() return return_value diff --git a/hahomematic/platforms/generic/entity.py b/hahomematic/platforms/generic/entity.py index d64fe50d..cbaca81a 100644 --- a/hahomematic/platforms/generic/entity.py +++ b/hahomematic/platforms/generic/entity.py @@ -82,6 +82,7 @@ async def send_value( self, value: hme.InputParameterT, collector: hme.CallParameterCollector | None = None, + collector_order: int = 50, do_validate: bool = True, ) -> None: """Send value to ccu, or use collector if set.""" @@ -98,7 +99,7 @@ async def send_value( converted_value = self._convert_value(value=prepared_value) if collector: - collector.add_entity(self, value=converted_value) + collector.add_entity(self, value=converted_value, collector_order=collector_order) return if self._validate_state_change and not self.is_state_change(value=converted_value): diff --git a/pyproject.toml b/pyproject.toml index 870066f6..db1bfed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hahomematic" -version = "2024.1.0" +version = "2024.1.1" license = {text = "MIT License"} description = "Homematic interface for Home Assistant running on Python 3." readme = "README.md" diff --git a/tests/test_light.py b/tests/test_light.py index e192dcb7..9bcad719 100644 --- a/tests/test_light.py +++ b/tests/test_light.py @@ -263,12 +263,14 @@ async def test_cecolordimmereffect(factory: helper.Factory) -> None: assert light.hs_color == (0.0, 100.0) await light.turn_on(effect="Slow color change") + assert mock_client.method_calls[-2] == call.set_value( - channel_address="VCU3747418:3", paramset_key="VALUES", parameter="PROGRAM", value=1 + channel_address="VCU3747418:1", paramset_key="VALUES", parameter="LEVEL", value=1.0 ) assert mock_client.method_calls[-1] == call.set_value( - channel_address="VCU3747418:1", paramset_key="VALUES", parameter="LEVEL", value=1.0 + channel_address="VCU3747418:3", paramset_key="VALUES", parameter="PROGRAM", value=1 ) + assert light.effect == "Slow color change" central.event(const.INTERFACE_ID, "VCU3747418:2", "COLOR", 201) @@ -286,6 +288,17 @@ async def test_cecolordimmereffect(factory: helper.Factory) -> None: await light.turn_off() assert call_count == len(mock_client.method_calls) + await light.turn_on(brightness=28, effect="Slow color change") + assert mock_client.method_calls[-2] == call.set_value( + channel_address="VCU3747418:1", + paramset_key="VALUES", + parameter="LEVEL", + value=0.10980392156862745, + ) + assert mock_client.method_calls[-1] == call.set_value( + channel_address="VCU3747418:3", paramset_key="VALUES", parameter="PROGRAM", value=1 + ) + @pytest.mark.asyncio async def test_cecolortempdimmer(factory: helper.Factory) -> None: