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

Check/convert values of manual executed put_paramset/set_value #1663

Merged
merged 5 commits into from
Aug 25, 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.6.1
rev: v0.6.2
hooks:
- id: ruff
args:
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Version 2024.8.13 (2024-08-25)

- Check/convert values of manual executed put_paramset/set_value

# Version 2024.8.12 (2024-08-24)

- Add additional validation on config parameters
Expand Down
54 changes: 27 additions & 27 deletions hahomematic/central/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,38 +1049,38 @@ def get_parameters(
channels[channel_address].get(paramset_key.value, {}).items()
):
p_operations = paramset[Description.OPERATIONS]
for operation in operations:
if all(p_operations & operation for operation in operations):
if un_ignore_candidates_only and (

if all(p_operations & operation for operation in operations):
if un_ignore_candidates_only and (
(
(
(
generic_entity := self.get_generic_entity(
channel_address=channel_address,
parameter=parameter,
paramset_key=paramset_key,
)
generic_entity := self.get_generic_entity(
channel_address=channel_address,
parameter=parameter,
paramset_key=paramset_key,
)
and generic_entity.enabled_default
and not generic_entity.is_un_ignored
)
or parameter in IGNORE_FOR_UN_IGNORE_PARAMETERS
):
continue

if not full_format:
parameters.add(parameter)
continue

channel = (
UN_IGNORE_WILDCARD
if use_channel_wildcard
else get_channel_no(channel_address)
and generic_entity.enabled_default
and not generic_entity.is_un_ignored
)
or parameter in IGNORE_FOR_UN_IGNORE_PARAMETERS
):
continue

if not full_format:
parameters.add(parameter)
continue

channel = (
UN_IGNORE_WILDCARD
if use_channel_wildcard
else get_channel_no(channel_address)
)

full_parameter = f"{parameter}:{paramset_key}@{device_type}:"
if channel is not None:
full_parameter += str(channel)
parameters.add(full_parameter)
full_parameter = f"{parameter}:{paramset_key}@{device_type}:"
if channel is not None:
full_parameter += str(channel)
parameters.add(full_parameter)

return list(parameters)

Expand Down
109 changes: 98 additions & 11 deletions hahomematic/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@
ForcedDeviceAvailability,
InterfaceEventType,
InterfaceName,
Operations,
ParameterType,
ParamsetKey,
ProductGroup,
ProgramData,
ProxyInitState,
SystemInformation,
SystemVariableData,
)
from hahomematic.exceptions import BaseHomematicException, NoConnection
from hahomematic.exceptions import BaseHomematicException, HaHomematicException, NoConnection
from hahomematic.performance import measure_execution_time
from hahomematic.platforms.device import HmDevice
from hahomematic.platforms.support import convert_value
from hahomematic.support import (
build_headers,
build_xml_rpc_uri,
Expand Down Expand Up @@ -429,17 +432,28 @@ async def _set_value(
value: Any,
wait_for_callback: int | None,
rx_mode: str | None = None,
check_against_pd: bool = False,
) -> set[ENTITY_KEY]:
"""Set single value on paramset VALUES."""
try:
_LOGGER.debug("SET_VALUE: %s, %s, %s", channel_address, parameter, value)
checked_value = (
self._check_set_value(
channel_address=channel_address,
paramset_key=ParamsetKey.VALUES,
parameter=parameter,
value=value,
)
if check_against_pd
else value
)
_LOGGER.debug("SET_VALUE: %s, %s, %s", channel_address, parameter, checked_value)
if rx_mode:
await self._proxy.setValue(channel_address, parameter, value, rx_mode)
await self._proxy.setValue(channel_address, parameter, checked_value, rx_mode)
else:
await self._proxy.setValue(channel_address, parameter, value)
await self._proxy.setValue(channel_address, parameter, checked_value)
# store the send value in the last_value_send_cache
entity_keys = self._last_value_send_cache.add_set_value(
channel_address=channel_address, parameter=parameter, value=value
channel_address=channel_address, parameter=parameter, value=checked_value
)
if wait_for_callback is not None and (
device := self.central.get_device(
Expand All @@ -449,7 +463,7 @@ async def _set_value(
await wait_for_state_change_or_timeout(
device=device,
entity_keys=entity_keys,
values={parameter: value},
values={parameter: checked_value},
wait_for_callback=wait_for_callback,
)
return entity_keys # noqa: TRY300
Expand All @@ -464,6 +478,18 @@ async def _set_value(
)
return set()

def _check_set_value(
self, channel_address: str, paramset_key: str, parameter: str, value: Any
) -> Any:
"""Check set_value."""
return self._convert_value(
channel_address=channel_address,
paramset_key=paramset_key,
parameter=parameter,
value=value,
operation=Operations.WRITE,
)

async def set_value(
self,
channel_address: str,
Expand All @@ -472,6 +498,7 @@ async def set_value(
value: Any,
wait_for_callback: int | None = WAIT_FOR_CALLBACK,
rx_mode: str | None = None,
check_against_pd: bool = False,
) -> set[ENTITY_KEY]:
"""Set single value on paramset VALUES."""
if paramset_key == ParamsetKey.VALUES:
Expand All @@ -481,13 +508,15 @@ async def set_value(
value=value,
wait_for_callback=wait_for_callback,
rx_mode=rx_mode,
check_against_pd=check_against_pd,
)
return await self.put_paramset(
channel_address=channel_address,
paramset_key=paramset_key,
values={parameter: value},
wait_for_callback=wait_for_callback,
rx_mode=rx_mode,
check_against_pd=check_against_pd,
)

async def get_paramset(self, address: str, paramset_key: str) -> dict[str, Any]:
Expand Down Expand Up @@ -522,6 +551,7 @@ async def put_paramset(
values: dict[str, Any],
wait_for_callback: int | None = WAIT_FOR_CALLBACK,
rx_mode: str | None = None,
check_against_pd: bool = False,
) -> set[ENTITY_KEY]:
"""
Set paramsets manually.
Expand All @@ -530,16 +560,27 @@ async def put_paramset(
but for bidcos devices there is a master paramset at the device.
"""
try:
_LOGGER.debug("PUT_PARAMSET: %s, %s, %s", channel_address, paramset_key, values)
checked_values = (
self._check_put_paramset(
channel_address=channel_address, paramset_key=paramset_key, values=values
)
if check_against_pd
else values
)
_LOGGER.debug(
"PUT_PARAMSET: %s, %s, %s", channel_address, paramset_key, checked_values
)
if rx_mode:
await self._proxy.putParamset(channel_address, paramset_key, values, rx_mode)
await self._proxy.putParamset(
channel_address, paramset_key, checked_values, rx_mode
)
else:
await self._proxy.putParamset(channel_address, paramset_key, values)
await self._proxy.putParamset(channel_address, paramset_key, checked_values)
# store the send value in the last_value_send_cache
entity_keys = self._last_value_send_cache.add_put_paramset(
channel_address=channel_address,
paramset_key=paramset_key,
values=values,
values=checked_values,
)
if wait_for_callback is not None and (
device := self.central.get_device(
Expand All @@ -549,7 +590,7 @@ async def put_paramset(
await wait_for_state_change_or_timeout(
device=device,
entity_keys=entity_keys,
values=values,
values=checked_values,
wait_for_callback=wait_for_callback,
)
return entity_keys # noqa: TRY300
Expand All @@ -564,6 +605,52 @@ async def put_paramset(
)
return set()

def _check_put_paramset(
self, channel_address: str, paramset_key: str, values: dict[str, Any]
) -> dict[str, Any]:
"""Check put_paramset."""
checked_values: dict[str, Any] = {}
for param, value in values.items():
checked_values[param] = self._convert_value(
channel_address=channel_address,
paramset_key=paramset_key,
parameter=param,
value=value,
operation=Operations.WRITE,
)
return checked_values

def _convert_value(
self,
channel_address: str,
paramset_key: str,
parameter: str,
value: Any,
operation: Operations,
) -> Any:
"""Check a single parameter against paramset descriptions."""
if parameter_data := self.central.paramset_descriptions.get_parameter_data(
interface_id=self.interface_id,
channel_address=channel_address,
paramset_key=paramset_key,
parameter=parameter,
):
pd_type = ParameterType(parameter_data[Description.TYPE])
pd_value_list = (
tuple(parameter_data[Description.VALUE_LIST])
if Description.VALUE_LIST in parameter_data
else None
)
if not bool(int(parameter_data[Description.OPERATIONS]) & operation):
raise HaHomematicException(
f"Parameter {parameter} does not support the requested operation {operation.value}"
)

return convert_value(value=value, target_type=pd_type, value_list=pd_value_list)
raise HaHomematicException(
f"Parameter {parameter} could not be found: {self.interface_id}/{channel_address}/{paramset_key}"
)

async def fetch_paramset_description(
self, channel_address: str, paramset_key: str, save_to_file: bool = True
) -> None:
Expand Down
2 changes: 2 additions & 0 deletions hahomematic_support/client_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ async def set_value(
value: Any,
wait_for_callback: int | None = WAIT_FOR_CALLBACK,
rx_mode: str | None = None,
check_against_pd: bool = False,
) -> set[ENTITY_KEY]:
"""Set single value on paramset VALUES."""
# store the send value in the last_value_send_cache
Expand Down Expand Up @@ -253,6 +254,7 @@ async def put_paramset(
values: Any,
wait_for_callback: int | None = WAIT_FOR_CALLBACK,
rx_mode: str | None = None,
check_against_pd: bool = False,
) -> set[ENTITY_KEY]:
"""
Set paramsets manually.
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.8.12"
version = "2024.8.13"
license = {text = "MIT License"}
description = "Homematic interface for Home Assistant running on Python 3."
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pylint-per-file-ignores==1.3.2
pylint-strict-informational==0.1
pylint==3.2.6
pytest-aiohttp==1.0.5
pytest-asyncio==0.23.8
pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-rerunfailures==14.0
pytest-socket==0.7.0
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_pre_commit.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bandit==1.7.9
codespell==2.3.0
ruff==0.6.1
ruff==0.6.2
yamllint==1.35.1