Skip to content

Commit

Permalink
Allow ordered execution of collector paramsets (#1356)
Browse files Browse the repository at this point in the history
  • Loading branch information
SukramJ authored Jan 5, 2024
1 parent 05c7f4c commit 04841e0
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 29 deletions.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 4 additions & 2 deletions hahomematic/platforms/custom/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
54 changes: 31 additions & 23 deletions hahomematic/platforms/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]] = {
Expand Down Expand Up @@ -729,55 +731,61 @@ 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:
"""Wrap method to add collector."""
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
Expand Down
3 changes: 2 additions & 1 deletion hahomematic/platforms/generic/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
17 changes: 15 additions & 2 deletions tests/test_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down

0 comments on commit 04841e0

Please sign in to comment.