aiohomematic 2025.10.20b0__tar.gz → 2025.10.22__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.10.20b0/aiohomematic.egg-info → aiohomematic-2025.10.22}/PKG-INFO +1 -1
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/central/__init__.py +55 -1
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/client/__init__.py +17 -15
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/const.py +3 -3
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/data_point.py +5 -5
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/support.py +2 -2
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/store/dynamic.py +74 -95
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/store/visibility.py +1 -1
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/support.py +1 -53
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22/aiohomematic.egg-info}/PKG-INFO +1 -1
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/pyproject.toml +1 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/LICENSE +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/MANIFEST.in +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/README.md +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/async_support.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/central/decorators.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/central/rpc_server.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/client/_rpc_errors.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/client/json_rpc.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/client/rpc_proxy.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/context.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/converter.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/decorators.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/exceptions.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/hmcli.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/climate.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/data_point.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/support.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/climate.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/const.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/cover.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/definition.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/light.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/lock.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/siren.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/support.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/switch.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/valve.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/data_point.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/device.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/event.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/action.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/binary_sensor.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/button.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/data_point.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/number.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/select.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/sensor.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/switch.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/text.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/binary_sensor.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/button.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/data_point.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/number.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/select.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/sensor.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/switch.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/text.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/update.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/property_decorators.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/py.typed +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/get_serial.fn +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/store/__init__.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/store/persistent.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/validator.py +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic.egg-info/SOURCES.txt +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic.egg-info/dependency_links.txt +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic.egg-info/requires.txt +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic.egg-info/top_level.txt +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/requirements.txt +0 -0
- {aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.22
|
|
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>
|
|
@@ -110,6 +110,7 @@ from aiohomematic.const import (
|
|
|
110
110
|
DEVICE_FIRMWARE_CHECK_INTERVAL,
|
|
111
111
|
DEVICE_FIRMWARE_DELIVERING_CHECK_INTERVAL,
|
|
112
112
|
DEVICE_FIRMWARE_UPDATING_CHECK_INTERVAL,
|
|
113
|
+
IDENTIFIER_SEPARATOR,
|
|
113
114
|
IGNORE_FOR_UN_IGNORE_PARAMETERS,
|
|
114
115
|
IP_ANY_V4,
|
|
115
116
|
LOCAL_HOST,
|
|
@@ -173,12 +174,16 @@ from aiohomematic.store import (
|
|
|
173
174
|
from aiohomematic.support import (
|
|
174
175
|
LogContextMixin,
|
|
175
176
|
PayloadMixin,
|
|
176
|
-
|
|
177
|
+
check_or_create_directory,
|
|
178
|
+
check_password,
|
|
177
179
|
extract_device_addresses_from_device_descriptions,
|
|
178
180
|
extract_exc_args,
|
|
179
181
|
get_channel_no,
|
|
180
182
|
get_device_address,
|
|
181
183
|
get_ip_addr,
|
|
184
|
+
is_hostname,
|
|
185
|
+
is_ipv4_address,
|
|
186
|
+
is_port,
|
|
182
187
|
)
|
|
183
188
|
|
|
184
189
|
__all__ = ["CentralConfig", "CentralUnit", "INTERFACE_EVENT_SCHEMA"]
|
|
@@ -2219,6 +2224,55 @@ class CentralConnectionState:
|
|
|
2219
2224
|
)
|
|
2220
2225
|
|
|
2221
2226
|
|
|
2227
|
+
def check_config(
|
|
2228
|
+
*,
|
|
2229
|
+
central_name: str,
|
|
2230
|
+
host: str,
|
|
2231
|
+
username: str,
|
|
2232
|
+
password: str,
|
|
2233
|
+
storage_directory: str,
|
|
2234
|
+
callback_host: str | None,
|
|
2235
|
+
callback_port_xml_rpc: int | None,
|
|
2236
|
+
json_port: int | None,
|
|
2237
|
+
interface_configs: AbstractSet[hmcl.InterfaceConfig] | None = None,
|
|
2238
|
+
) -> list[str]:
|
|
2239
|
+
"""Check config. Throws BaseHomematicException on failure."""
|
|
2240
|
+
config_failures: list[str] = []
|
|
2241
|
+
if central_name and IDENTIFIER_SEPARATOR in central_name:
|
|
2242
|
+
config_failures.append(f"Instance name must not contain {IDENTIFIER_SEPARATOR}")
|
|
2243
|
+
|
|
2244
|
+
if not (is_hostname(hostname=host) or is_ipv4_address(address=host)):
|
|
2245
|
+
config_failures.append("Invalid hostname or ipv4 address")
|
|
2246
|
+
if not username:
|
|
2247
|
+
config_failures.append("Username must not be empty")
|
|
2248
|
+
if not password:
|
|
2249
|
+
config_failures.append("Password is required")
|
|
2250
|
+
if not check_password(password=password):
|
|
2251
|
+
config_failures.append("Password is not valid")
|
|
2252
|
+
try:
|
|
2253
|
+
check_or_create_directory(directory=storage_directory)
|
|
2254
|
+
except BaseHomematicException as bhexc:
|
|
2255
|
+
config_failures.append(extract_exc_args(exc=bhexc)[0])
|
|
2256
|
+
if callback_host and not (is_hostname(hostname=callback_host) or is_ipv4_address(address=callback_host)):
|
|
2257
|
+
config_failures.append("Invalid callback hostname or ipv4 address")
|
|
2258
|
+
if callback_port_xml_rpc and not is_port(port=callback_port_xml_rpc):
|
|
2259
|
+
config_failures.append("Invalid xml rpc callback port")
|
|
2260
|
+
if json_port and not is_port(port=json_port):
|
|
2261
|
+
config_failures.append("Invalid json port")
|
|
2262
|
+
if interface_configs and not _has_primary_client(interface_configs=interface_configs):
|
|
2263
|
+
config_failures.append(f"No primary interface ({', '.join(PRIMARY_CLIENT_CANDIDATE_INTERFACES)}) defined")
|
|
2264
|
+
|
|
2265
|
+
return config_failures
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
def _has_primary_client(*, interface_configs: AbstractSet[hmcl.InterfaceConfig]) -> bool:
|
|
2269
|
+
"""Check if all configured clients exists in central."""
|
|
2270
|
+
for interface_config in interface_configs:
|
|
2271
|
+
if interface_config.interface in PRIMARY_CLIENT_CANDIDATE_INTERFACES:
|
|
2272
|
+
return True
|
|
2273
|
+
return False
|
|
2274
|
+
|
|
2275
|
+
|
|
2222
2276
|
def _get_new_data_points(
|
|
2223
2277
|
*,
|
|
2224
2278
|
new_devices: set[Device],
|
|
@@ -58,8 +58,7 @@ from aiohomematic import central as hmcu
|
|
|
58
58
|
from aiohomematic.client.json_rpc import AioJsonRpcAioHttpClient
|
|
59
59
|
from aiohomematic.client.rpc_proxy import AioXmlRpcProxy, BaseRpcProxy
|
|
60
60
|
from aiohomematic.const import (
|
|
61
|
-
|
|
62
|
-
CALLBACK_WARN_DISARM_INTERVAL,
|
|
61
|
+
CALLBACK_WARN_INTERVAL,
|
|
63
62
|
DATETIME_FORMAT_MILLIS,
|
|
64
63
|
DEFAULT_MAX_WORKERS,
|
|
65
64
|
DP_KEY_VALUE,
|
|
@@ -146,6 +145,7 @@ class Client(ABC, LogContextMixin):
|
|
|
146
145
|
self._last_value_send_cache = CommandCache(interface_id=client_config.interface_id)
|
|
147
146
|
self._available: bool = True
|
|
148
147
|
self._connection_error_count: int = 0
|
|
148
|
+
self._is_callback_alive: bool = True
|
|
149
149
|
self._is_initialized: bool = False
|
|
150
150
|
self._ping_pong_cache: Final = PingPongCache(
|
|
151
151
|
central=client_config.central, interface_id=client_config.interface_id
|
|
@@ -395,7 +395,7 @@ class Client(ABC, LogContextMixin):
|
|
|
395
395
|
return False
|
|
396
396
|
if not self.supports_push_updates:
|
|
397
397
|
return True
|
|
398
|
-
return (datetime.now() - self.modified_at).total_seconds() <
|
|
398
|
+
return (datetime.now() - self.modified_at).total_seconds() < CALLBACK_WARN_INTERVAL
|
|
399
399
|
|
|
400
400
|
def is_callback_alive(self) -> bool:
|
|
401
401
|
"""Return if XmlRPC-Server is alive based on received events for this client."""
|
|
@@ -404,29 +404,31 @@ class Client(ABC, LogContextMixin):
|
|
|
404
404
|
if (
|
|
405
405
|
last_events_dt := self.central.get_last_event_seen_for_interface(interface_id=self.interface_id)
|
|
406
406
|
) is not None:
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
407
|
+
if (seconds_since_last_event := (datetime.now() - last_events_dt).total_seconds()) > CALLBACK_WARN_INTERVAL:
|
|
408
|
+
if self._is_callback_alive:
|
|
409
|
+
self.central.fire_interface_event(
|
|
410
|
+
interface_id=self.interface_id,
|
|
411
|
+
interface_event_type=InterfaceEventType.CALLBACK,
|
|
412
|
+
data={
|
|
413
|
+
EventKey.AVAILABLE: False,
|
|
414
|
+
EventKey.SECONDS_SINCE_LAST_EVENT: int(seconds_since_last_event),
|
|
415
|
+
},
|
|
416
|
+
)
|
|
417
|
+
self._is_callback_alive = False
|
|
418
418
|
_LOGGER.warning(
|
|
419
419
|
"IS_CALLBACK_ALIVE: Callback for %s has not received events for %is",
|
|
420
420
|
self.interface_id,
|
|
421
421
|
seconds_since_last_event,
|
|
422
422
|
)
|
|
423
423
|
return False
|
|
424
|
-
|
|
424
|
+
|
|
425
|
+
if not self._is_callback_alive:
|
|
425
426
|
self.central.fire_interface_event(
|
|
426
427
|
interface_id=self.interface_id,
|
|
427
428
|
interface_event_type=InterfaceEventType.CALLBACK,
|
|
428
429
|
data={EventKey.AVAILABLE: True},
|
|
429
430
|
)
|
|
431
|
+
self._is_callback_alive = True
|
|
430
432
|
return True
|
|
431
433
|
|
|
432
434
|
@abstractmethod
|
|
@@ -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.10.
|
|
22
|
+
VERSION: Final = "2025.10.22"
|
|
23
23
|
|
|
24
24
|
# Detect test speedup mode via environment
|
|
25
25
|
_TEST_SPEEDUP: Final = (
|
|
@@ -133,8 +133,7 @@ WAIT_FOR_CALLBACK: Final[int | None] = None
|
|
|
133
133
|
SCHEDULER_NOT_STARTED_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 10
|
|
134
134
|
SCHEDULER_LOOP_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 5
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
CALLBACK_WARN_DISARM_INTERVAL: Final = CONNECTION_CHECKER_INTERVAL * 20
|
|
136
|
+
CALLBACK_WARN_INTERVAL: Final = CONNECTION_CHECKER_INTERVAL * 40
|
|
138
137
|
|
|
139
138
|
# Path
|
|
140
139
|
PROGRAM_SET_PATH_ROOT: Final = "program/set"
|
|
@@ -304,6 +303,7 @@ class EventKey(StrEnum):
|
|
|
304
303
|
INTERFACE_ID = "interface_id"
|
|
305
304
|
MODEL = "model"
|
|
306
305
|
PARAMETER = "parameter"
|
|
306
|
+
PONG_MISMATCH_ACCEPTABLE = "pong_mismatch_allowed"
|
|
307
307
|
PONG_MISMATCH_COUNT = "pong_mismatch_count"
|
|
308
308
|
SECONDS_SINCE_LAST_EVENT = "seconds_since_last_event"
|
|
309
309
|
TYPE = "type"
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/data_point.py
RENAMED
|
@@ -204,12 +204,12 @@ class CustomDataPoint(BaseDataPoint):
|
|
|
204
204
|
def _init_data_points(self) -> None:
|
|
205
205
|
"""Init data point collection."""
|
|
206
206
|
# Add repeating fields
|
|
207
|
-
for field_name, parameter in self._device_def.get(
|
|
207
|
+
for field_name, parameter in self._device_def.get(CDPD.REPEATABLE_FIELDS, {}).items():
|
|
208
208
|
if dp := self._device.get_generic_data_point(channel_address=self._channel.address, parameter=parameter):
|
|
209
209
|
self._add_data_point(field=field_name, data_point=dp, is_visible=False)
|
|
210
210
|
|
|
211
211
|
# Add visible repeating fields
|
|
212
|
-
for field_name, parameter in self._device_def.get(
|
|
212
|
+
for field_name, parameter in self._device_def.get(CDPD.VISIBLE_REPEATABLE_FIELDS, {}).items():
|
|
213
213
|
if dp := self._device.get_generic_data_point(channel_address=self._channel.address, parameter=parameter):
|
|
214
214
|
self._add_data_point(field=field_name, data_point=dp, is_visible=True)
|
|
215
215
|
|
|
@@ -229,11 +229,11 @@ class CustomDataPoint(BaseDataPoint):
|
|
|
229
229
|
|
|
230
230
|
# Add device fields
|
|
231
231
|
self._add_data_points(
|
|
232
|
-
field_dict_name=
|
|
232
|
+
field_dict_name=CDPD.FIELDS,
|
|
233
233
|
)
|
|
234
234
|
# Add visible device fields
|
|
235
235
|
self._add_data_points(
|
|
236
|
-
field_dict_name=
|
|
236
|
+
field_dict_name=CDPD.VISIBLE_FIELDS,
|
|
237
237
|
is_visible=True,
|
|
238
238
|
)
|
|
239
239
|
|
|
@@ -243,7 +243,7 @@ class CustomDataPoint(BaseDataPoint):
|
|
|
243
243
|
if hmed.get_include_default_data_points(device_profile=self._device_profile):
|
|
244
244
|
self._mark_data_points(custom_data_point_def=hmed.get_default_data_points())
|
|
245
245
|
|
|
246
|
-
def _add_data_points(self, *, field_dict_name:
|
|
246
|
+
def _add_data_points(self, *, field_dict_name: CDPD, is_visible: bool | None = None) -> None:
|
|
247
247
|
"""Add data points to custom data point."""
|
|
248
248
|
fields = self._device_def.get(field_dict_name, {})
|
|
249
249
|
for channel_no, channel in fields.items():
|
|
@@ -32,7 +32,7 @@ from aiohomematic.const import (
|
|
|
32
32
|
ParameterType,
|
|
33
33
|
)
|
|
34
34
|
from aiohomematic.model import device as hmd
|
|
35
|
-
from aiohomematic.model.custom import
|
|
35
|
+
from aiohomematic.model.custom.const import CDPD
|
|
36
36
|
from aiohomematic.support import to_bool
|
|
37
37
|
|
|
38
38
|
__all__ = [
|
|
@@ -565,7 +565,7 @@ def check_channel_is_the_only_primary_channel(
|
|
|
565
565
|
device_has_multiple_channels: bool,
|
|
566
566
|
) -> bool:
|
|
567
567
|
"""Check if this channel is the only primary channel."""
|
|
568
|
-
primary_channel: int = device_def[
|
|
568
|
+
primary_channel: int = device_def[CDPD.PRIMARY_CHANNEL]
|
|
569
569
|
return bool(primary_channel == current_channel_no and device_has_multiple_channels is False)
|
|
570
570
|
|
|
571
571
|
|
|
@@ -387,36 +387,17 @@ class PingPongCache:
|
|
|
387
387
|
self._unknown_pong_logged: bool = False
|
|
388
388
|
|
|
389
389
|
@property
|
|
390
|
-
def
|
|
391
|
-
"""
|
|
392
|
-
self.
|
|
393
|
-
return len(self._pending_pongs) > self._allowed_delta
|
|
394
|
-
|
|
395
|
-
@property
|
|
396
|
-
def high_unknown_pongs(self) -> bool:
|
|
397
|
-
"""Check, if store contains too many unknown pongs."""
|
|
398
|
-
self._cleanup_unknown_pongs()
|
|
399
|
-
return len(self._unknown_pongs) > self._allowed_delta
|
|
400
|
-
|
|
401
|
-
@property
|
|
402
|
-
def low_pending_pongs(self) -> bool:
|
|
403
|
-
"""Return True when pending pong count is at or below the allowed delta (i.e., not high)."""
|
|
404
|
-
self._cleanup_pending_pongs()
|
|
405
|
-
return len(self._pending_pongs) <= self._allowed_delta
|
|
406
|
-
|
|
407
|
-
@property
|
|
408
|
-
def low_unknown_pongs(self) -> bool:
|
|
409
|
-
"""Return True when unknown pong count is at or below the allowed delta (i.e., not high)."""
|
|
410
|
-
self._cleanup_unknown_pongs()
|
|
411
|
-
return len(self._unknown_pongs) <= self._allowed_delta
|
|
390
|
+
def allowed_delta(self) -> int:
|
|
391
|
+
"""Return the allowed delta."""
|
|
392
|
+
return self._allowed_delta
|
|
412
393
|
|
|
413
394
|
@property
|
|
414
|
-
def
|
|
395
|
+
def _pending_pong_count(self) -> int:
|
|
415
396
|
"""Return the pending pong count."""
|
|
416
397
|
return len(self._pending_pongs)
|
|
417
398
|
|
|
418
399
|
@property
|
|
419
|
-
def
|
|
400
|
+
def _unknown_pong_count(self) -> int:
|
|
420
401
|
"""Return the unknown pong count."""
|
|
421
402
|
return len(self._unknown_pongs)
|
|
422
403
|
|
|
@@ -430,18 +411,16 @@ class PingPongCache:
|
|
|
430
411
|
def handle_send_ping(self, *, ping_ts: datetime) -> None:
|
|
431
412
|
"""Handle send ping timestamp."""
|
|
432
413
|
self._pending_pongs.add(ping_ts)
|
|
414
|
+
self._cleanup_pending_pongs()
|
|
433
415
|
# Throttle event emission to every second ping to avoid spamming callbacks,
|
|
434
416
|
# but always emit when crossing the high threshold.
|
|
435
|
-
count = self.
|
|
417
|
+
count = self._pending_pong_count
|
|
436
418
|
if (count > self._allowed_delta) or (count % 2 == 0):
|
|
437
|
-
self._check_and_fire_pong_event(
|
|
438
|
-
event_type=InterfaceEventType.PENDING_PONG,
|
|
439
|
-
pong_mismatch_count=count,
|
|
440
|
-
)
|
|
419
|
+
self._check_and_fire_pong_event(event_type=InterfaceEventType.PENDING_PONG)
|
|
441
420
|
_LOGGER.debug(
|
|
442
421
|
"PING PONG CACHE: Increase pending PING count: %s - %i for ts: %s",
|
|
443
422
|
self._interface_id,
|
|
444
|
-
|
|
423
|
+
count,
|
|
445
424
|
ping_ts,
|
|
446
425
|
)
|
|
447
426
|
|
|
@@ -449,59 +428,56 @@ class PingPongCache:
|
|
|
449
428
|
"""Handle received pong timestamp."""
|
|
450
429
|
if pong_ts in self._pending_pongs:
|
|
451
430
|
self._pending_pongs.remove(pong_ts)
|
|
452
|
-
self.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
)
|
|
431
|
+
self._cleanup_pending_pongs()
|
|
432
|
+
count = self._pending_pong_count
|
|
433
|
+
self._check_and_fire_pong_event(event_type=InterfaceEventType.PENDING_PONG)
|
|
456
434
|
_LOGGER.debug(
|
|
457
435
|
"PING PONG CACHE: Reduce pending PING count: %s - %i for ts: %s",
|
|
458
436
|
self._interface_id,
|
|
459
|
-
|
|
437
|
+
count,
|
|
438
|
+
pong_ts,
|
|
439
|
+
)
|
|
440
|
+
else:
|
|
441
|
+
self._unknown_pongs.add(pong_ts)
|
|
442
|
+
self._cleanup_unknown_pongs()
|
|
443
|
+
count = self._unknown_pong_count
|
|
444
|
+
self._check_and_fire_pong_event(event_type=InterfaceEventType.UNKNOWN_PONG)
|
|
445
|
+
_LOGGER.debug(
|
|
446
|
+
"PING PONG CACHE: Increase unknown PONG count: %s - %i for ts: %s",
|
|
447
|
+
self._interface_id,
|
|
448
|
+
count,
|
|
460
449
|
pong_ts,
|
|
461
450
|
)
|
|
462
|
-
return
|
|
463
|
-
|
|
464
|
-
self._unknown_pongs.add(pong_ts)
|
|
465
|
-
self._check_and_fire_pong_event(
|
|
466
|
-
event_type=InterfaceEventType.UNKNOWN_PONG,
|
|
467
|
-
pong_mismatch_count=self.unknown_pong_count,
|
|
468
|
-
)
|
|
469
|
-
_LOGGER.debug(
|
|
470
|
-
"PING PONG CACHE: Increase unknown PONG count: %s - %i for ts: %s",
|
|
471
|
-
self._interface_id,
|
|
472
|
-
self.unknown_pong_count,
|
|
473
|
-
pong_ts,
|
|
474
|
-
)
|
|
475
451
|
|
|
476
452
|
def _cleanup_pending_pongs(self) -> None:
|
|
477
453
|
"""Cleanup too old pending pongs."""
|
|
478
454
|
dt_now = datetime.now()
|
|
479
|
-
for
|
|
455
|
+
for pp_pong_ts in list(self._pending_pongs):
|
|
480
456
|
# Only expire entries that are actually older than the TTL.
|
|
481
|
-
if (dt_now -
|
|
482
|
-
self._pending_pongs.remove(
|
|
457
|
+
if (dt_now - pp_pong_ts).total_seconds() > self._ttl:
|
|
458
|
+
self._pending_pongs.remove(pp_pong_ts)
|
|
483
459
|
_LOGGER.debug(
|
|
484
460
|
"PING PONG CACHE: Removing expired pending PONG: %s - %i for ts: %s",
|
|
485
461
|
self._interface_id,
|
|
486
|
-
self.
|
|
487
|
-
|
|
462
|
+
self._pending_pong_count,
|
|
463
|
+
pp_pong_ts,
|
|
488
464
|
)
|
|
489
465
|
|
|
490
466
|
def _cleanup_unknown_pongs(self) -> None:
|
|
491
467
|
"""Cleanup too old unknown pongs."""
|
|
492
468
|
dt_now = datetime.now()
|
|
493
|
-
for
|
|
469
|
+
for up_pong_ts in list(self._unknown_pongs):
|
|
494
470
|
# Only expire entries that are actually older than the TTL.
|
|
495
|
-
if (dt_now -
|
|
496
|
-
self._unknown_pongs.remove(
|
|
471
|
+
if (dt_now - up_pong_ts).total_seconds() > self._ttl:
|
|
472
|
+
self._unknown_pongs.remove(up_pong_ts)
|
|
497
473
|
_LOGGER.debug(
|
|
498
474
|
"PING PONG CACHE: Removing expired unknown PONG: %s - %i or ts: %s",
|
|
499
475
|
self._interface_id,
|
|
500
|
-
self.
|
|
501
|
-
|
|
476
|
+
self._unknown_pong_count,
|
|
477
|
+
up_pong_ts,
|
|
502
478
|
)
|
|
503
479
|
|
|
504
|
-
def _check_and_fire_pong_event(self, *, event_type: InterfaceEventType
|
|
480
|
+
def _check_and_fire_pong_event(self, *, event_type: InterfaceEventType) -> None:
|
|
505
481
|
"""Fire an event about the pong status."""
|
|
506
482
|
|
|
507
483
|
def _fire_event(mismatch_count: int) -> None:
|
|
@@ -515,6 +491,7 @@ class PingPongCache:
|
|
|
515
491
|
EventKey.TYPE: event_type,
|
|
516
492
|
EventKey.DATA: {
|
|
517
493
|
EventKey.CENTRAL_NAME: self._central.name,
|
|
494
|
+
EventKey.PONG_MISMATCH_ACCEPTABLE: mismatch_count <= self._allowed_delta,
|
|
518
495
|
EventKey.PONG_MISMATCH_COUNT: mismatch_count,
|
|
519
496
|
},
|
|
520
497
|
}
|
|
@@ -522,44 +499,46 @@ class PingPongCache:
|
|
|
522
499
|
),
|
|
523
500
|
)
|
|
524
501
|
|
|
525
|
-
if
|
|
502
|
+
if event_type == InterfaceEventType.PENDING_PONG:
|
|
503
|
+
self._cleanup_pending_pongs()
|
|
504
|
+
count = self._pending_pong_count
|
|
505
|
+
if self._pending_pong_count > self._allowed_delta:
|
|
506
|
+
# Emit interface event to inform subscribers about high pending pong count.
|
|
507
|
+
_fire_event(mismatch_count=count)
|
|
508
|
+
if self._pending_pong_logged is False:
|
|
509
|
+
_LOGGER.warning(
|
|
510
|
+
"Pending PONG mismatch: There is a mismatch between send ping events and received pong events for instance %s. "
|
|
511
|
+
"Possible reason 1: You are running multiple instances with the same instance name configured for this integration. "
|
|
512
|
+
"Re-add one instance! Otherwise this instance will not receive update events from your CCU. "
|
|
513
|
+
"Possible reason 2: Something is stuck on the CCU or hasn't been cleaned up. Therefore, try a CCU restart."
|
|
514
|
+
"Possible reason 3: Your setup is misconfigured and this instance is not able to receive events from the CCU.",
|
|
515
|
+
self._interface_id,
|
|
516
|
+
)
|
|
517
|
+
self._pending_pong_logged = True
|
|
526
518
|
# In low state:
|
|
527
519
|
# - If we previously logged a high state, emit a reset event (mismatch=0) exactly once.
|
|
528
520
|
# - Otherwise, throttle emission to every second ping (even counts > 0) to avoid spamming.
|
|
529
|
-
|
|
521
|
+
elif self._pending_pong_logged:
|
|
530
522
|
_fire_event(mismatch_count=0)
|
|
531
523
|
self._pending_pong_logged = False
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
)
|
|
554
|
-
self._pending_pong_logged = True
|
|
555
|
-
|
|
556
|
-
if self.high_unknown_pongs and event_type == InterfaceEventType.UNKNOWN_PONG:
|
|
557
|
-
if self._unknown_pong_logged is False:
|
|
558
|
-
_LOGGER.warning(
|
|
559
|
-
"Unknown PONG Mismatch: Your instance %s receives PONG events, that it hasn't send. "
|
|
560
|
-
"Possible reason 1: You are running multiple instances with the same instance name configured for this integration. "
|
|
561
|
-
"Re-add one instance! Otherwise the other instance will not receive update events from your CCU. "
|
|
562
|
-
"Possible reason 2: Something is stuck on the CCU or hasn't been cleaned up. Therefore, try a CCU restart.",
|
|
563
|
-
self._interface_id,
|
|
564
|
-
)
|
|
565
|
-
self._unknown_pong_logged = True
|
|
524
|
+
elif count > 0 and count % 2 == 0:
|
|
525
|
+
_fire_event(mismatch_count=count)
|
|
526
|
+
elif event_type == InterfaceEventType.UNKNOWN_PONG:
|
|
527
|
+
self._cleanup_unknown_pongs()
|
|
528
|
+
count = self._unknown_pong_count
|
|
529
|
+
if self._unknown_pong_count > self._allowed_delta:
|
|
530
|
+
# Emit interface event to inform subscribers about high unknown pong count.
|
|
531
|
+
_fire_event(mismatch_count=count)
|
|
532
|
+
if self._unknown_pong_logged is False:
|
|
533
|
+
_LOGGER.warning(
|
|
534
|
+
"Unknown PONG Mismatch: Your instance %s receives PONG events, that it hasn't send. "
|
|
535
|
+
"Possible reason 1: You are running multiple instances with the same instance name configured for this integration. "
|
|
536
|
+
"Re-add one instance! Otherwise the other instance will not receive update events from your CCU. "
|
|
537
|
+
"Possible reason 2: Something is stuck on the CCU or hasn't been cleaned up. Therefore, try a CCU restart.",
|
|
538
|
+
self._interface_id,
|
|
539
|
+
)
|
|
540
|
+
self._unknown_pong_logged = True
|
|
541
|
+
else:
|
|
542
|
+
# For unknown pongs, only reset the logged flag when we drop below the threshold.
|
|
543
|
+
# We do not emit an event here since there is no explicit expectation for a reset notification.
|
|
544
|
+
self._unknown_pong_logged = False
|
|
@@ -317,7 +317,7 @@ _IGNORE_PARAMETERS_BY_DEVICE: Final[Mapping[Parameter, frozenset[TModelName]]] =
|
|
|
317
317
|
"HmIP-WGT",
|
|
318
318
|
}
|
|
319
319
|
),
|
|
320
|
-
Parameter.VALVE_STATE: frozenset({"HmIPW-FALMOT-C12", "HmIP-FALMOT-C12"}),
|
|
320
|
+
Parameter.VALVE_STATE: frozenset({"HmIP-FALMOT-C8", "HmIPW-FALMOT-C12", "HmIP-FALMOT-C12"}),
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
_IGNORE_PARAMETERS_BY_DEVICE_LOWER: Final[dict[TParameterName, frozenset[TModelName]]] = {
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import base64
|
|
12
12
|
from collections import defaultdict
|
|
13
|
-
from collections.abc import Callable, Collection, Mapping
|
|
13
|
+
from collections.abc import Callable, Collection, Mapping
|
|
14
14
|
import contextlib
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from datetime import datetime
|
|
@@ -30,7 +30,6 @@ from typing import Any, Final, cast
|
|
|
30
30
|
|
|
31
31
|
import orjson
|
|
32
32
|
|
|
33
|
-
from aiohomematic import client as hmcl
|
|
34
33
|
from aiohomematic.const import (
|
|
35
34
|
ADDRESS_SEPARATOR,
|
|
36
35
|
ALLOWED_HOSTNAME_PATTERN,
|
|
@@ -38,12 +37,10 @@ from aiohomematic.const import (
|
|
|
38
37
|
CHANNEL_ADDRESS_PATTERN,
|
|
39
38
|
DEVICE_ADDRESS_PATTERN,
|
|
40
39
|
HTMLTAG_PATTERN,
|
|
41
|
-
IDENTIFIER_SEPARATOR,
|
|
42
40
|
INIT_DATETIME,
|
|
43
41
|
ISO_8859_1,
|
|
44
42
|
MAX_CACHE_AGE,
|
|
45
43
|
NO_CACHE_ENTRY,
|
|
46
|
-
PRIMARY_CLIENT_CANDIDATE_INTERFACES,
|
|
47
44
|
TIMEOUT,
|
|
48
45
|
UTF_8,
|
|
49
46
|
CommandRxMode,
|
|
@@ -95,55 +92,6 @@ def build_xml_rpc_headers(
|
|
|
95
92
|
return [("Authorization", f"Basic {base64_message}")]
|
|
96
93
|
|
|
97
94
|
|
|
98
|
-
def check_config(
|
|
99
|
-
*,
|
|
100
|
-
central_name: str,
|
|
101
|
-
host: str,
|
|
102
|
-
username: str,
|
|
103
|
-
password: str,
|
|
104
|
-
storage_directory: str,
|
|
105
|
-
callback_host: str | None,
|
|
106
|
-
callback_port_xml_rpc: int | None,
|
|
107
|
-
json_port: int | None,
|
|
108
|
-
interface_configs: AbstractSet[hmcl.InterfaceConfig] | None = None,
|
|
109
|
-
) -> list[str]:
|
|
110
|
-
"""Check config. Throws BaseHomematicException on failure."""
|
|
111
|
-
config_failures: list[str] = []
|
|
112
|
-
if central_name and IDENTIFIER_SEPARATOR in central_name:
|
|
113
|
-
config_failures.append(f"Instance name must not contain {IDENTIFIER_SEPARATOR}")
|
|
114
|
-
|
|
115
|
-
if not (is_hostname(hostname=host) or is_ipv4_address(address=host)):
|
|
116
|
-
config_failures.append("Invalid hostname or ipv4 address")
|
|
117
|
-
if not username:
|
|
118
|
-
config_failures.append("Username must not be empty")
|
|
119
|
-
if not password:
|
|
120
|
-
config_failures.append("Password is required")
|
|
121
|
-
if not check_password(password=password):
|
|
122
|
-
config_failures.append("Password is not valid")
|
|
123
|
-
try:
|
|
124
|
-
check_or_create_directory(directory=storage_directory)
|
|
125
|
-
except BaseHomematicException as bhexc:
|
|
126
|
-
config_failures.append(extract_exc_args(exc=bhexc)[0])
|
|
127
|
-
if callback_host and not (is_hostname(hostname=callback_host) or is_ipv4_address(address=callback_host)):
|
|
128
|
-
config_failures.append("Invalid callback hostname or ipv4 address")
|
|
129
|
-
if callback_port_xml_rpc and not is_port(port=callback_port_xml_rpc):
|
|
130
|
-
config_failures.append("Invalid xml rpc callback port")
|
|
131
|
-
if json_port and not is_port(port=json_port):
|
|
132
|
-
config_failures.append("Invalid json port")
|
|
133
|
-
if interface_configs and not has_primary_client(interface_configs=interface_configs):
|
|
134
|
-
config_failures.append(f"No primary interface ({', '.join(PRIMARY_CLIENT_CANDIDATE_INTERFACES)}) defined")
|
|
135
|
-
|
|
136
|
-
return config_failures
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def has_primary_client(*, interface_configs: AbstractSet[hmcl.InterfaceConfig]) -> bool:
|
|
140
|
-
"""Check if all configured clients exists in central."""
|
|
141
|
-
for interface_config in interface_configs:
|
|
142
|
-
if interface_config.interface in PRIMARY_CLIENT_CANDIDATE_INTERFACES:
|
|
143
|
-
return True
|
|
144
|
-
return False
|
|
145
|
-
|
|
146
|
-
|
|
147
95
|
def delete_file(directory: str, file_name: str) -> None: # kwonly: disable
|
|
148
96
|
"""Delete the file. File can contain a wildcard."""
|
|
149
97
|
if os.path.exists(directory):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.22
|
|
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>
|
|
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.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/__init__.py
RENAMED
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/climate.py
RENAMED
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/data_point.py
RENAMED
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/calculated/support.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/custom/definition.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
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/binary_sensor.py
RENAMED
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/generic/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
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/model/hub/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
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/get_serial.fn
RENAMED
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic/rega_scripts/set_program_state.fn
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic-2025.10.20b0 → aiohomematic-2025.10.22}/aiohomematic.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|