Skip to content

Commit

Permalink
Check/convert values of manual executed put_paramset/set_value (#1663)
Browse files Browse the repository at this point in the history
* Check/convert values of manual executed put_paramset

* Check/convert values of manual executed set_value

* Update __init__.py

* Update requirements

* Update requirements
  • Loading branch information
SukramJ authored Aug 25, 2024
1 parent c588ea0 commit 97af9ac
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 42 deletions.
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

0 comments on commit 97af9ac

Please sign in to comment.