Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ordered execution of collector paramsets #1356

Merged
merged 1 commit into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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