aiohomematic 2025.9.8__tar.gz → 2025.10.1__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.8 → aiohomematic-2025.10.1}/PKG-INFO +1 -1
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/central/__init__.py +6 -4
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/client/xml_rpc.py +1 -1
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/const.py +3 -1
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/climate.py +85 -12
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/cover.py +12 -2
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/data_point.py +1 -1
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/data_point.py +2 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic.egg-info/PKG-INFO +1 -1
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_climate.py +68 -9
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/LICENSE +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/README.md +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/async_support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/caches/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/caches/dynamic.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/caches/persistent.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/caches/visibility.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/central/decorators.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/central/xml_rpc_server.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/client/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/client/_rpc_errors.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/client/json_rpc.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/context.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/converter.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/decorators.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/exceptions.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/hmcli.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/calculated/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/calculated/climate.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/calculated/data_point.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/calculated/support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/const.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/data_point.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/definition.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/light.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/lock.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/siren.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/switch.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/custom/valve.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/device.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/event.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/action.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/binary_sensor.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/button.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/number.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/select.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/sensor.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/switch.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/generic/text.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/binary_sensor.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/button.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/data_point.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/number.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/select.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/sensor.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/switch.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/hub/text.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/model/update.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/property_decorators.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/py.typed +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/get_serial.fn +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/validator.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic.egg-info/SOURCES.txt +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic.egg-info/dependency_links.txt +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic.egg-info/requires.txt +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic.egg-info/top_level.txt +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic_support/__init__.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic_support/client_local.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/pyproject.toml +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/setup.cfg +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_action.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_binary_sensor.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_button.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_calculated_support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_central.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_central_pydevccu.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_cover.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_decorator.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_device.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_entity.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_event.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_json_rpc.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_light.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_lock.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_number.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_select.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_sensor.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_siren.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_support.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_switch.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/tests/test_text.py +0 -0
- {aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/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.1
|
|
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>
|
|
@@ -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,10 +100,12 @@ 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,
|
|
108
107
|
DEFAULT_UN_IGNORES,
|
|
108
|
+
DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE,
|
|
109
109
|
DEFAULT_VERIFY_TLS,
|
|
110
110
|
DEVICE_FIRMWARE_CHECK_INTERVAL,
|
|
111
111
|
DEVICE_FIRMWARE_DELIVERING_CHECK_INTERVAL,
|
|
@@ -1828,16 +1828,15 @@ class CentralConfig:
|
|
|
1828
1828
|
def __init__(
|
|
1829
1829
|
self,
|
|
1830
1830
|
central_id: str,
|
|
1831
|
-
default_callback_port: int,
|
|
1832
1831
|
host: str,
|
|
1833
1832
|
interface_configs: AbstractSet[hmcl.InterfaceConfig],
|
|
1834
1833
|
name: str,
|
|
1835
1834
|
password: str,
|
|
1836
|
-
storage_folder: str,
|
|
1837
1835
|
username: str,
|
|
1838
1836
|
client_session: ClientSession | None = None,
|
|
1839
1837
|
callback_host: str | None = None,
|
|
1840
1838
|
callback_port: int | None = None,
|
|
1839
|
+
default_callback_port: int = PORT_ANY,
|
|
1841
1840
|
enable_device_firmware_check: bool = DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK,
|
|
1842
1841
|
enable_program_scan: bool = DEFAULT_ENABLE_PROGRAM_SCAN,
|
|
1843
1842
|
enable_sysvar_scan: bool = DEFAULT_ENABLE_SYSVAR_SCAN,
|
|
@@ -1851,10 +1850,12 @@ class CentralConfig:
|
|
|
1851
1850
|
periodic_refresh_interval: int = DEFAULT_PERIODIC_REFRESH_INTERVAL,
|
|
1852
1851
|
program_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_PROGRAM_MARKERS,
|
|
1853
1852
|
start_direct: bool = False,
|
|
1853
|
+
storage_folder: str = DEFAULT_STORAGE_FOLDER,
|
|
1854
1854
|
sys_scan_interval: int = DEFAULT_SYS_SCAN_INTERVAL,
|
|
1855
1855
|
sysvar_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_SYSVAR_MARKERS,
|
|
1856
1856
|
tls: bool = DEFAULT_TLS,
|
|
1857
1857
|
un_ignore_list: frozenset[str] = DEFAULT_UN_IGNORES,
|
|
1858
|
+
use_group_channel_for_cover_state: bool = DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE,
|
|
1858
1859
|
verify_tls: bool = DEFAULT_VERIFY_TLS,
|
|
1859
1860
|
) -> None:
|
|
1860
1861
|
"""Init the client config."""
|
|
@@ -1885,6 +1886,7 @@ class CentralConfig:
|
|
|
1885
1886
|
self.sysvar_markers: Final = sysvar_markers
|
|
1886
1887
|
self.tls: Final = tls
|
|
1887
1888
|
self.un_ignore_list: Final = un_ignore_list
|
|
1889
|
+
self.use_group_channel_for_cover_state: Final = use_group_channel_for_cover_state
|
|
1888
1890
|
self.username: Final = username
|
|
1889
1891
|
self.verify_tls: Final = verify_tls
|
|
1890
1892
|
|
|
@@ -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.1"
|
|
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
|
|
@@ -44,6 +45,7 @@ DEFAULT_SYSVAR_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
|
|
|
44
45
|
DEFAULT_SYS_SCAN_INTERVAL: Final = 30
|
|
45
46
|
DEFAULT_TLS: Final = False
|
|
46
47
|
DEFAULT_UN_IGNORES: Final[frozenset[str]] = frozenset()
|
|
48
|
+
DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE: Final = True
|
|
47
49
|
DEFAULT_VERIFY_TLS: Final = False
|
|
48
50
|
|
|
49
51
|
# Default encoding for json service calls, persistent cache
|
|
@@ -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)
|
|
@@ -100,6 +100,7 @@ class CustomDpCover(CustomDataPoint):
|
|
|
100
100
|
"_dp_group_level",
|
|
101
101
|
"_dp_level",
|
|
102
102
|
"_dp_stop",
|
|
103
|
+
"_use_group_channel_for_cover_state",
|
|
103
104
|
)
|
|
104
105
|
_category = DataPointCategory.COVER
|
|
105
106
|
_closed_level: float = _CLOSED_LEVEL
|
|
@@ -118,11 +119,16 @@ class CustomDpCover(CustomDataPoint):
|
|
|
118
119
|
self._dp_group_level: DpSensor[float | None] = self._get_data_point(
|
|
119
120
|
field=Field.GROUP_LEVEL, data_point_type=DpSensor[float | None]
|
|
120
121
|
)
|
|
122
|
+
self._use_group_channel_for_cover_state = self.central.config.use_group_channel_for_cover_state
|
|
121
123
|
|
|
122
124
|
@property
|
|
123
125
|
def _group_level(self) -> float:
|
|
124
126
|
"""Return the channel level of the cover."""
|
|
125
|
-
if
|
|
127
|
+
if (
|
|
128
|
+
self._use_group_channel_for_cover_state
|
|
129
|
+
and self._dp_group_level.value is not None
|
|
130
|
+
and self.usage == DataPointUsage.CDP_PRIMARY
|
|
131
|
+
):
|
|
126
132
|
return float(self._dp_group_level.value)
|
|
127
133
|
return self._dp_level.value if self._dp_level.value is not None else self._closed_level
|
|
128
134
|
|
|
@@ -276,7 +282,11 @@ class CustomDpBlind(CustomDpCover):
|
|
|
276
282
|
@property
|
|
277
283
|
def _group_tilt_level(self) -> float:
|
|
278
284
|
"""Return the group level of the tilt."""
|
|
279
|
-
if
|
|
285
|
+
if (
|
|
286
|
+
self._use_group_channel_for_cover_state
|
|
287
|
+
and self._dp_group_level_2.value is not None
|
|
288
|
+
and self.usage == DataPointUsage.CDP_PRIMARY
|
|
289
|
+
):
|
|
280
290
|
return float(self._dp_group_level_2.value)
|
|
281
291
|
return self._dp_level_2.value if self._dp_level_2.value is not None else self._closed_level
|
|
282
292
|
|
|
@@ -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.1
|
|
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>
|
|
@@ -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)
|
|
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
|
{aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/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
|
{aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/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.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/fetch_all_device_data.fn
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/aiohomematic/rega_scripts/set_program_state.fn
RENAMED
|
File without changes
|
{aiohomematic-2025.9.8 → aiohomematic-2025.10.1}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|