aiohomematic 2025.9.7__tar.gz → 2025.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aiohomematic might be problematic. Click here for more details.
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/PKG-INFO +1 -1
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/caches/visibility.py +2 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/central/__init__.py +3 -4
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/client/xml_rpc.py +1 -1
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/const.py +2 -1
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/climate.py +85 -12
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/data_point.py +1 -1
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/data_point.py +2 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic.egg-info/PKG-INFO +1 -1
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_central_pydevccu.py +5 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_climate.py +68 -9
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_light.py +2 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_switch.py +1 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/LICENSE +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/README.md +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/async_support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/caches/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/caches/dynamic.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/caches/persistent.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/central/decorators.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/central/xml_rpc_server.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/client/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/client/_rpc_errors.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/client/json_rpc.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/context.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/converter.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/decorators.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/exceptions.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/hmcli.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/calculated/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/calculated/climate.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/calculated/data_point.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/calculated/support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/const.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/cover.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/data_point.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/definition.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/light.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/lock.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/siren.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/switch.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/custom/valve.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/device.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/event.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/action.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/binary_sensor.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/button.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/number.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/select.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/sensor.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/switch.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/text.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/binary_sensor.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/button.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/data_point.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/number.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/select.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/sensor.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/switch.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/hub/text.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/update.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/property_decorators.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/py.typed +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/get_serial.fn +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/validator.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic.egg-info/SOURCES.txt +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic.egg-info/dependency_links.txt +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic.egg-info/requires.txt +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic.egg-info/top_level.txt +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic_support/__init__.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic_support/client_local.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/pyproject.toml +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/setup.cfg +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_action.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_binary_sensor.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_button.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_calculated_support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_central.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_cover.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_decorator.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_device.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_entity.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_event.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_json_rpc.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_lock.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_number.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_select.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_sensor.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_siren.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_support.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_text.py +0 -0
- {aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/tests/test_valve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.10.0
|
|
4
4
|
Summary: Homematic interface for Home Assistant running on Python 3.
|
|
5
5
|
Home-page: https://github.com/sukramj/aiohomematic
|
|
6
6
|
Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
|
|
@@ -171,6 +171,8 @@ _IGNORED_PARAMETERS: Final[frozenset[TParameterName]] = frozenset(
|
|
|
171
171
|
"CLEAR_ERROR",
|
|
172
172
|
"CLEAR_WINDOW_OPEN_SYMBOL",
|
|
173
173
|
"CLOCK",
|
|
174
|
+
"CMD_RETL", # CuXD
|
|
175
|
+
"CMD_RETS", # CuXD
|
|
174
176
|
"CONTROL_DIFFERENTIAL_TEMPERATURE",
|
|
175
177
|
"DATE_TIME_UNKNOWN",
|
|
176
178
|
"DECISION_VALUE",
|
|
@@ -44,12 +44,10 @@ Example (simplified):
|
|
|
44
44
|
|
|
45
45
|
cfg = CentralConfig(
|
|
46
46
|
central_id="ccu-main",
|
|
47
|
-
default_callback_port=43439,
|
|
48
47
|
host="ccu.local",
|
|
49
48
|
interface_configs=iface_cfgs,
|
|
50
49
|
name="MyCCU",
|
|
51
50
|
password="secret",
|
|
52
|
-
storage_folder=".storage",
|
|
53
51
|
username="admin",
|
|
54
52
|
)
|
|
55
53
|
|
|
@@ -102,6 +100,7 @@ from aiohomematic.const import (
|
|
|
102
100
|
DEFAULT_MAX_READ_WORKERS,
|
|
103
101
|
DEFAULT_PERIODIC_REFRESH_INTERVAL,
|
|
104
102
|
DEFAULT_PROGRAM_MARKERS,
|
|
103
|
+
DEFAULT_STORAGE_FOLDER,
|
|
105
104
|
DEFAULT_SYS_SCAN_INTERVAL,
|
|
106
105
|
DEFAULT_SYSVAR_MARKERS,
|
|
107
106
|
DEFAULT_TLS,
|
|
@@ -1828,16 +1827,15 @@ class CentralConfig:
|
|
|
1828
1827
|
def __init__(
|
|
1829
1828
|
self,
|
|
1830
1829
|
central_id: str,
|
|
1831
|
-
default_callback_port: int,
|
|
1832
1830
|
host: str,
|
|
1833
1831
|
interface_configs: AbstractSet[hmcl.InterfaceConfig],
|
|
1834
1832
|
name: str,
|
|
1835
1833
|
password: str,
|
|
1836
|
-
storage_folder: str,
|
|
1837
1834
|
username: str,
|
|
1838
1835
|
client_session: ClientSession | None = None,
|
|
1839
1836
|
callback_host: str | None = None,
|
|
1840
1837
|
callback_port: int | None = None,
|
|
1838
|
+
default_callback_port: int = PORT_ANY,
|
|
1841
1839
|
enable_device_firmware_check: bool = DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK,
|
|
1842
1840
|
enable_program_scan: bool = DEFAULT_ENABLE_PROGRAM_SCAN,
|
|
1843
1841
|
enable_sysvar_scan: bool = DEFAULT_ENABLE_SYSVAR_SCAN,
|
|
@@ -1851,6 +1849,7 @@ class CentralConfig:
|
|
|
1851
1849
|
periodic_refresh_interval: int = DEFAULT_PERIODIC_REFRESH_INTERVAL,
|
|
1852
1850
|
program_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_PROGRAM_MARKERS,
|
|
1853
1851
|
start_direct: bool = False,
|
|
1852
|
+
storage_folder: str = DEFAULT_STORAGE_FOLDER,
|
|
1854
1853
|
sys_scan_interval: int = DEFAULT_SYS_SCAN_INTERVAL,
|
|
1855
1854
|
sysvar_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_SYSVAR_MARKERS,
|
|
1856
1855
|
tls: bool = DEFAULT_TLS,
|
|
@@ -203,7 +203,7 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
|
|
|
203
203
|
if not self._connection_state.has_issue(issuer=self, iid=self._interface_id):
|
|
204
204
|
if perr.errmsg == "Unauthorized":
|
|
205
205
|
raise AuthFailure(perr) from perr
|
|
206
|
-
raise NoConnectionException(perr.errmsg) from perr
|
|
206
|
+
raise NoConnectionException(f"No connection to {self.log_context} ({perr.errmsg})") from perr
|
|
207
207
|
except Exception as exc:
|
|
208
208
|
raise ClientException(exc) from exc
|
|
209
209
|
|
|
@@ -19,7 +19,7 @@ import sys
|
|
|
19
19
|
from types import MappingProxyType
|
|
20
20
|
from typing import Any, Final, NamedTuple, Required, TypeAlias, TypedDict
|
|
21
21
|
|
|
22
|
-
VERSION: Final = "2025.
|
|
22
|
+
VERSION: Final = "2025.10.0"
|
|
23
23
|
|
|
24
24
|
# Detect test speedup mode via environment
|
|
25
25
|
_TEST_SPEEDUP: Final = (
|
|
@@ -27,6 +27,7 @@ _TEST_SPEEDUP: Final = (
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
# default
|
|
30
|
+
DEFAULT_STORAGE_FOLDER: Final = "aiohomematic_storage"
|
|
30
31
|
DEFAULT_CUSTOM_ID: Final = "custom_id"
|
|
31
32
|
DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK: Final = False
|
|
32
33
|
DEFAULT_ENABLE_PROGRAM_SCAN: Final = True
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
from abc import abstractmethod
|
|
7
8
|
from collections.abc import Mapping
|
|
8
9
|
from datetime import datetime, timedelta
|
|
9
10
|
from enum import IntEnum, StrEnum
|
|
@@ -26,7 +27,16 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
|
|
|
26
27
|
from aiohomematic.model.custom.data_point import CustomDataPoint
|
|
27
28
|
from aiohomematic.model.custom.support import CustomConfig
|
|
28
29
|
from aiohomematic.model.data_point import CallParameterCollector, bind_collector
|
|
29
|
-
from aiohomematic.model.generic import
|
|
30
|
+
from aiohomematic.model.generic import (
|
|
31
|
+
DpAction,
|
|
32
|
+
DpBinarySensor,
|
|
33
|
+
DpFloat,
|
|
34
|
+
DpInteger,
|
|
35
|
+
DpSelect,
|
|
36
|
+
DpSensor,
|
|
37
|
+
DpSwitch,
|
|
38
|
+
GenericDataPoint,
|
|
39
|
+
)
|
|
30
40
|
from aiohomematic.property_decorators import config_property, state_property
|
|
31
41
|
|
|
32
42
|
_LOGGER: Final = logging.getLogger(__name__)
|
|
@@ -174,6 +184,7 @@ class BaseCustomDpClimate(CustomDataPoint):
|
|
|
174
184
|
"_dp_temperature",
|
|
175
185
|
"_dp_temperature_maximum",
|
|
176
186
|
"_dp_temperature_minimum",
|
|
187
|
+
"_old_manu_setpoint",
|
|
177
188
|
"_supports_schedule",
|
|
178
189
|
)
|
|
179
190
|
_category = DataPointCategory.CLIMATE
|
|
@@ -199,6 +210,7 @@ class BaseCustomDpClimate(CustomDataPoint):
|
|
|
199
210
|
custom_config=custom_config,
|
|
200
211
|
)
|
|
201
212
|
self._supports_schedule = False
|
|
213
|
+
self._old_manu_setpoint: float | None = None
|
|
202
214
|
|
|
203
215
|
def _init_data_point_fields(self) -> None:
|
|
204
216
|
"""Init the data_point fields."""
|
|
@@ -219,6 +231,13 @@ class BaseCustomDpClimate(CustomDataPoint):
|
|
|
219
231
|
self._dp_temperature_minimum: DpFloat = self._get_data_point(
|
|
220
232
|
field=Field.TEMPERATURE_MINIMUM, data_point_type=DpFloat
|
|
221
233
|
)
|
|
234
|
+
self._unregister_callbacks.append(
|
|
235
|
+
self._dp_setpoint.register_data_point_updated_callback(cb=self._manu_temp_changed, custom_id="manu_temp")
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def _manu_temp_changed(self, data_point: GenericDataPoint) -> None:
|
|
240
|
+
"""Handle device state changes."""
|
|
222
241
|
|
|
223
242
|
@state_property
|
|
224
243
|
def current_humidity(self) -> int | None:
|
|
@@ -311,11 +330,21 @@ class BaseCustomDpClimate(CustomDataPoint):
|
|
|
311
330
|
return _TEMP_CELSIUS
|
|
312
331
|
|
|
313
332
|
@property
|
|
314
|
-
def
|
|
315
|
-
"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
333
|
+
def _temperature_for_heat_mode(self) -> float:
|
|
334
|
+
"""
|
|
335
|
+
Return a safe temperature to use when setting mode to HEAT.
|
|
336
|
+
|
|
337
|
+
If the current target temperature is None or represents the special OFF value,
|
|
338
|
+
fall back to the device's minimum valid temperature. Otherwise, return the
|
|
339
|
+
current target temperature clipped to the valid [min, max] range.
|
|
340
|
+
"""
|
|
341
|
+
temp = self._old_manu_setpoint or self.target_temperature
|
|
342
|
+
# Treat None or OFF sentinel as invalid/unsafe to restore.
|
|
343
|
+
if temp is None or temp <= _OFF_TEMPERATURE or temp < self.min_temp:
|
|
344
|
+
return self.min_temp if self.min_temp > _OFF_TEMPERATURE else _OFF_TEMPERATURE + 0.5
|
|
345
|
+
if temp > self.max_temp:
|
|
346
|
+
return self.max_temp
|
|
347
|
+
return temp
|
|
319
348
|
|
|
320
349
|
@property
|
|
321
350
|
def schedule_profile_nos(self) -> int:
|
|
@@ -329,10 +358,7 @@ class BaseCustomDpClimate(CustomDataPoint):
|
|
|
329
358
|
collector: CallParameterCollector | None = None,
|
|
330
359
|
do_validate: bool = True,
|
|
331
360
|
) -> None:
|
|
332
|
-
"""Set new target temperature."""
|
|
333
|
-
if not self.is_state_change(temperature=temperature):
|
|
334
|
-
return
|
|
335
|
-
|
|
361
|
+
"""Set new target temperature. The temperature must be set in all cases, even if the values are identical."""
|
|
336
362
|
if do_validate and self.mode == ClimateMode.HEAT and self.min_max_value_not_relevant_for_manu_mode:
|
|
337
363
|
do_validate = False
|
|
338
364
|
|
|
@@ -694,6 +720,9 @@ class CustomDpSimpleRfThermostat(BaseCustomDpClimate):
|
|
|
694
720
|
|
|
695
721
|
__slots__ = ()
|
|
696
722
|
|
|
723
|
+
def _manu_temp_changed(self, data_point: GenericDataPoint) -> None:
|
|
724
|
+
"""Handle device state changes."""
|
|
725
|
+
|
|
697
726
|
|
|
698
727
|
class CustomDpRfThermostat(BaseCustomDpClimate):
|
|
699
728
|
"""Classic Homematic thermostat like HM-CC-RT-DN."""
|
|
@@ -753,6 +782,28 @@ class CustomDpRfThermostat(BaseCustomDpClimate):
|
|
|
753
782
|
field=Field.WEEK_PROGRAM_POINTER, data_point_type=DpSelect
|
|
754
783
|
)
|
|
755
784
|
|
|
785
|
+
self._unregister_callbacks.append(
|
|
786
|
+
self._dp_control_mode.register_data_point_updated_callback(
|
|
787
|
+
cb=self._manu_temp_changed, custom_id="manu_temp"
|
|
788
|
+
)
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
def _manu_temp_changed(self, data_point: GenericDataPoint) -> None:
|
|
792
|
+
"""Handle device state changes."""
|
|
793
|
+
if (
|
|
794
|
+
data_point == self._dp_control_mode
|
|
795
|
+
and self.mode == ClimateMode.HEAT
|
|
796
|
+
and self._dp_setpoint.refreshed_recently
|
|
797
|
+
):
|
|
798
|
+
self._old_manu_setpoint = self.target_temperature
|
|
799
|
+
|
|
800
|
+
if (
|
|
801
|
+
data_point == self._dp_setpoint
|
|
802
|
+
and self.mode == ClimateMode.HEAT
|
|
803
|
+
and self._dp_control_mode.refreshed_recently
|
|
804
|
+
):
|
|
805
|
+
self._old_manu_setpoint = self.target_temperature
|
|
806
|
+
|
|
756
807
|
@state_property
|
|
757
808
|
def activity(self) -> ClimateActivity | None:
|
|
758
809
|
"""Return the current activity."""
|
|
@@ -817,7 +868,7 @@ class CustomDpRfThermostat(BaseCustomDpClimate):
|
|
|
817
868
|
if mode == ClimateMode.AUTO:
|
|
818
869
|
await self._dp_auto_mode.send_value(value=True, collector=collector)
|
|
819
870
|
elif mode == ClimateMode.HEAT:
|
|
820
|
-
await self._dp_manu_mode.send_value(value=self.
|
|
871
|
+
await self._dp_manu_mode.send_value(value=self._temperature_for_heat_mode, collector=collector)
|
|
821
872
|
elif mode == ClimateMode.OFF:
|
|
822
873
|
await self._dp_manu_mode.send_value(value=self.target_temperature, collector=collector)
|
|
823
874
|
# Disable validation here to allow setting a value,
|
|
@@ -975,6 +1026,28 @@ class CustomDpIpThermostat(BaseCustomDpClimate):
|
|
|
975
1026
|
field=Field.TEMPERATURE_OFFSET, data_point_type=DpFloat
|
|
976
1027
|
)
|
|
977
1028
|
|
|
1029
|
+
self._unregister_callbacks.append(
|
|
1030
|
+
self._dp_set_point_mode.register_data_point_updated_callback(
|
|
1031
|
+
cb=self._manu_temp_changed, custom_id="manu_temp"
|
|
1032
|
+
)
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
def _manu_temp_changed(self, data_point: GenericDataPoint) -> None:
|
|
1036
|
+
"""Handle device state changes."""
|
|
1037
|
+
if (
|
|
1038
|
+
data_point == self._dp_set_point_mode
|
|
1039
|
+
and self.mode == ClimateMode.HEAT
|
|
1040
|
+
and self._dp_setpoint.refreshed_recently
|
|
1041
|
+
):
|
|
1042
|
+
self._old_manu_setpoint = self.target_temperature
|
|
1043
|
+
|
|
1044
|
+
if (
|
|
1045
|
+
data_point == self._dp_setpoint
|
|
1046
|
+
and self.mode == ClimateMode.HEAT
|
|
1047
|
+
and self._dp_set_point_mode.refreshed_recently
|
|
1048
|
+
):
|
|
1049
|
+
self._old_manu_setpoint = self.target_temperature
|
|
1050
|
+
|
|
978
1051
|
@property
|
|
979
1052
|
def _is_heating_mode(self) -> bool:
|
|
980
1053
|
"""Return the heating_mode of the device."""
|
|
@@ -1074,7 +1147,7 @@ class CustomDpIpThermostat(BaseCustomDpClimate):
|
|
|
1074
1147
|
await self._dp_control_mode.send_value(value=_ModeHmIP.AUTO, collector=collector)
|
|
1075
1148
|
elif mode in (ClimateMode.HEAT, ClimateMode.COOL):
|
|
1076
1149
|
await self._dp_control_mode.send_value(value=_ModeHmIP.MANU, collector=collector)
|
|
1077
|
-
await self.set_temperature(temperature=self.
|
|
1150
|
+
await self.set_temperature(temperature=self._temperature_for_heat_mode, collector=collector)
|
|
1078
1151
|
elif mode == ClimateMode.OFF:
|
|
1079
1152
|
await self._dp_control_mode.send_value(value=_ModeHmIP.MANU, collector=collector)
|
|
1080
1153
|
await self.set_temperature(temperature=_OFF_TEMPERATURE, collector=collector, do_validate=False)
|
|
@@ -757,7 +757,7 @@ class BaseParameterDataPoint[
|
|
|
757
757
|
|
|
758
758
|
@property
|
|
759
759
|
def service(self) -> bool:
|
|
760
|
-
"""Return the if data_point is
|
|
760
|
+
"""Return the if data_point is relevant for service messages in the backend."""
|
|
761
761
|
return self._service
|
|
762
762
|
|
|
763
763
|
@property
|
|
@@ -107,10 +107,12 @@ class GenericDataPoint[ParameterT: GenericParameterType, InputParameterT: Generi
|
|
|
107
107
|
return set()
|
|
108
108
|
|
|
109
109
|
converted_value = self._convert_value(value=prepared_value)
|
|
110
|
+
# if collector is set, then add value to collector
|
|
110
111
|
if collector:
|
|
111
112
|
collector.add_data_point(self, value=converted_value, collector_order=collector_order)
|
|
112
113
|
return set()
|
|
113
114
|
|
|
115
|
+
# if collector is not set, then send value directly
|
|
114
116
|
if self._validate_state_change and not self.is_state_change(value=converted_value):
|
|
115
117
|
return set()
|
|
116
118
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.10.0
|
|
4
4
|
Summary: Homematic interface for Home Assistant running on Python 3.
|
|
5
5
|
Home-page: https://github.com/sukramj/aiohomematic
|
|
6
6
|
Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
|
|
@@ -55,6 +55,11 @@ async def test_central_full(central_unit_full) -> None: # noqa: C901
|
|
|
55
55
|
|
|
56
56
|
data = {}
|
|
57
57
|
for device in central_unit_full.devices:
|
|
58
|
+
if device.model in ("HmIP-BSM", "HmIP-BDT", "HmIP-PSM", "HmIP-FSM", "HmIP-WSM", "HmIP-SMO230-A"):
|
|
59
|
+
assert device.has_sub_devices is False
|
|
60
|
+
if device.model in ("HmIP-DRSI4", "HmIP-DRDI3", "HmIP-BSL"):
|
|
61
|
+
assert device.has_sub_devices is True
|
|
62
|
+
|
|
58
63
|
if device.model not in data:
|
|
59
64
|
data[device.model] = {}
|
|
60
65
|
for dp in device.generic_data_points:
|
|
@@ -379,15 +379,26 @@ async def test_cerfthermostat_with_profiles(
|
|
|
379
379
|
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "CONTROL_MODE", _ModeHmIP.MANU.value)
|
|
380
380
|
assert climate.mode == ClimateMode.HEAT
|
|
381
381
|
|
|
382
|
+
await climate.set_temperature(13.0)
|
|
383
|
+
assert mock_client.method_calls[-1] == call.set_value(
|
|
384
|
+
channel_address="VCU0000341:2",
|
|
385
|
+
paramset_key=ParamsetKey.VALUES,
|
|
386
|
+
parameter="SET_TEMPERATURE",
|
|
387
|
+
value=13.0,
|
|
388
|
+
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
389
|
+
)
|
|
390
|
+
assert climate._old_manu_setpoint == 13.0
|
|
391
|
+
|
|
382
392
|
await climate.set_mode(ClimateMode.OFF)
|
|
383
393
|
assert mock_client.method_calls[-1] == call.put_paramset(
|
|
384
394
|
channel_address="VCU0000341:2",
|
|
385
395
|
paramset_key_or_link_address=ParamsetKey.VALUES,
|
|
386
|
-
values={"MANU_MODE":
|
|
396
|
+
values={"MANU_MODE": 13.0, "SET_TEMPERATURE": 4.5},
|
|
387
397
|
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
388
398
|
)
|
|
389
399
|
|
|
390
400
|
assert climate.mode == ClimateMode.OFF
|
|
401
|
+
assert climate._old_manu_setpoint == 13.0
|
|
391
402
|
|
|
392
403
|
await climate.set_mode(ClimateMode.AUTO)
|
|
393
404
|
assert mock_client.method_calls[-1] == call.set_value(
|
|
@@ -397,10 +408,35 @@ async def test_cerfthermostat_with_profiles(
|
|
|
397
408
|
value=True,
|
|
398
409
|
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
399
410
|
)
|
|
400
|
-
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "CONTROL_MODE",
|
|
411
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "CONTROL_MODE", _ModeHmIP.AUTO.value)
|
|
401
412
|
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "SET_TEMPERATURE", 24.0)
|
|
402
413
|
assert climate.mode == ClimateMode.AUTO
|
|
414
|
+
assert climate._old_manu_setpoint == 13.0
|
|
415
|
+
assert climate.target_temperature == 24.0
|
|
416
|
+
await climate.set_mode(ClimateMode.HEAT)
|
|
417
|
+
assert mock_client.method_calls[-1] == call.set_value(
|
|
418
|
+
channel_address="VCU0000341:2",
|
|
419
|
+
paramset_key=ParamsetKey.VALUES,
|
|
420
|
+
parameter="MANU_MODE",
|
|
421
|
+
value=climate._temperature_for_heat_mode,
|
|
422
|
+
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
423
|
+
)
|
|
424
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "CONTROL_MODE", _ModeHmIP.MANU.value)
|
|
425
|
+
await central.data_point_event(
|
|
426
|
+
const.INTERFACE_ID, "VCU0000341:2", "SET_TEMPERATURE", climate._temperature_for_heat_mode
|
|
427
|
+
)
|
|
428
|
+
assert climate.mode == ClimateMode.HEAT
|
|
403
429
|
|
|
430
|
+
await climate.set_mode(ClimateMode.AUTO)
|
|
431
|
+
assert mock_client.method_calls[-1] == call.set_value(
|
|
432
|
+
channel_address="VCU0000341:2",
|
|
433
|
+
paramset_key=ParamsetKey.VALUES,
|
|
434
|
+
parameter="AUTO_MODE",
|
|
435
|
+
value=True,
|
|
436
|
+
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
437
|
+
)
|
|
438
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "CONTROL_MODE", _ModeHmIP.AUTO.value)
|
|
439
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU0000341:2", "SET_TEMPERATURE", 24.0)
|
|
404
440
|
assert climate.profile == ClimateProfile.WEEK_PROGRAM_1
|
|
405
441
|
assert climate.profiles == (
|
|
406
442
|
ClimateProfile.BOOST,
|
|
@@ -568,7 +604,7 @@ async def test_ceipthermostat(
|
|
|
568
604
|
assert climate.activity == ClimateActivity.HEAT
|
|
569
605
|
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:9", "STATE", 0)
|
|
570
606
|
assert climate.activity == ClimateActivity.IDLE
|
|
571
|
-
|
|
607
|
+
assert climate._old_manu_setpoint is None
|
|
572
608
|
assert climate.current_humidity is None
|
|
573
609
|
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "HUMIDITY", 75)
|
|
574
610
|
assert climate.current_humidity == 75
|
|
@@ -609,8 +645,18 @@ async def test_ceipthermostat(
|
|
|
609
645
|
values={"CONTROL_MODE": 1, "SET_POINT_TEMPERATURE": 5.0},
|
|
610
646
|
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
611
647
|
)
|
|
648
|
+
await climate.set_temperature(19.5)
|
|
649
|
+
assert mock_client.method_calls[-1] == call.set_value(
|
|
650
|
+
channel_address="VCU1769958:1",
|
|
651
|
+
paramset_key=ParamsetKey.VALUES,
|
|
652
|
+
parameter="SET_POINT_TEMPERATURE",
|
|
653
|
+
value=19.5,
|
|
654
|
+
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
655
|
+
)
|
|
656
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "SET_POINT_TEMPERATURE", 19.5)
|
|
612
657
|
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "SET_POINT_MODE", _ModeHmIP.MANU.value)
|
|
613
658
|
assert climate.mode == ClimateMode.HEAT
|
|
659
|
+
assert climate._old_manu_setpoint == 19.5
|
|
614
660
|
|
|
615
661
|
assert climate.profile == ClimateProfile.NONE
|
|
616
662
|
assert climate.profiles == (
|
|
@@ -648,13 +694,26 @@ async def test_ceipthermostat(
|
|
|
648
694
|
"week_program_5",
|
|
649
695
|
"week_program_6",
|
|
650
696
|
)
|
|
697
|
+
|
|
698
|
+
await climate.set_mode(ClimateMode.HEAT)
|
|
699
|
+
assert mock_client.method_calls[-1] == call.put_paramset(
|
|
700
|
+
channel_address="VCU1769958:1",
|
|
701
|
+
paramset_key_or_link_address=ParamsetKey.VALUES,
|
|
702
|
+
values={"BOOST_MODE": False, "CONTROL_MODE": 1, "SET_POINT_TEMPERATURE": climate._temperature_for_heat_mode},
|
|
703
|
+
wait_for_callback=None,
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "SET_POINT_TEMPERATURE", 19.5)
|
|
707
|
+
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "SET_POINT_MODE", _ModeHmIP.MANU.value)
|
|
708
|
+
assert climate.mode == ClimateMode.HEAT
|
|
709
|
+
assert climate.target_temperature == 19.5
|
|
710
|
+
|
|
651
711
|
await climate.set_profile(ClimateProfile.NONE)
|
|
652
|
-
assert mock_client.method_calls[-1] == call.
|
|
712
|
+
assert mock_client.method_calls[-1] == call.put_paramset(
|
|
653
713
|
channel_address="VCU1769958:1",
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
wait_for_callback=WAIT_FOR_CALLBACK,
|
|
714
|
+
paramset_key_or_link_address=ParamsetKey.VALUES,
|
|
715
|
+
values={"BOOST_MODE": False, "CONTROL_MODE": 1, "SET_POINT_TEMPERATURE": 19.5},
|
|
716
|
+
wait_for_callback=None,
|
|
658
717
|
)
|
|
659
718
|
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "SET_POINT_MODE", _ModeHmIP.AWAY.value)
|
|
660
719
|
assert climate.profile == ClimateProfile.AWAY
|
|
@@ -716,7 +775,7 @@ async def test_ceipthermostat(
|
|
|
716
775
|
await central.data_point_event(const.INTERFACE_ID, "VCU1769958:1", "SET_POINT_TEMPERATURE", 12.0)
|
|
717
776
|
call_count = len(mock_client.method_calls)
|
|
718
777
|
await climate.set_temperature(12.0)
|
|
719
|
-
assert call_count == len(mock_client.method_calls)
|
|
778
|
+
assert call_count + 1 == len(mock_client.method_calls)
|
|
720
779
|
|
|
721
780
|
await climate.set_mode(ClimateMode.AUTO)
|
|
722
781
|
call_count = len(mock_client.method_calls)
|
|
@@ -639,6 +639,7 @@ async def test_ceipfixedcolorlightwired(
|
|
|
639
639
|
light: CustomDpIpFixedColorLight = cast(
|
|
640
640
|
CustomDpIpFixedColorLight, helper.get_prepared_custom_data_point(central, "VCU4704397", 8)
|
|
641
641
|
)
|
|
642
|
+
assert light.channel.device.has_sub_devices is False
|
|
642
643
|
assert light.usage == DataPointUsage.CDP_PRIMARY
|
|
643
644
|
assert light.color_temp_kelvin is None
|
|
644
645
|
assert light.hs_color == (0.0, 0.0)
|
|
@@ -927,6 +928,7 @@ async def test_ceiprgbwlight(
|
|
|
927
928
|
light: CustomDpIpRGBWLight = cast(
|
|
928
929
|
CustomDpIpRGBWLight, helper.get_prepared_custom_data_point(central, "VCU5629873", 1)
|
|
929
930
|
)
|
|
931
|
+
assert light.channel.device.has_sub_devices is False
|
|
930
932
|
assert light.usage == DataPointUsage.CDP_PRIMARY
|
|
931
933
|
assert light.color_temp_kelvin is None
|
|
932
934
|
assert light.hs_color is None
|
|
@@ -45,6 +45,7 @@ async def test_ceswitch(
|
|
|
45
45
|
switch: CustomDpSwitch = cast(CustomDpSwitch, helper.get_prepared_custom_data_point(central, "VCU2128127", 4))
|
|
46
46
|
assert switch.usage == DataPointUsage.CDP_PRIMARY
|
|
47
47
|
assert switch.service_method_names == ("turn_off", "turn_on")
|
|
48
|
+
assert switch.channel.device.has_sub_devices is False
|
|
48
49
|
|
|
49
50
|
await switch.turn_off()
|
|
50
51
|
assert switch.value is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/calculated/data_point.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/model/generic/binary_sensor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/fetch_all_device_data.fn
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/set_program_state.fn
RENAMED
|
File without changes
|
{aiohomematic-2025.9.7 → aiohomematic-2025.10.0}/aiohomematic/rega_scripts/set_system_variable.fn
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|