aiohomematic 2025.10.4__tar.gz → 2025.10.6__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.4 → aiohomematic-2025.10.6}/PKG-INFO +1 -1
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/caches/visibility.py +2 -2
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/central/__init__.py +51 -46
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/central/decorators.py +2 -2
- aiohomematic-2025.10.4/aiohomematic/central/xml_rpc_server.py → aiohomematic-2025.10.6/aiohomematic/central/rpc_server.py +62 -47
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/client/__init__.py +118 -78
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/client/json_rpc.py +1 -1
- aiohomematic-2025.10.4/aiohomematic/client/xml_rpc.py → aiohomematic-2025.10.6/aiohomematic/client/rpc_proxy.py +78 -43
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/const.py +31 -4
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/switch.py +1 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/support.py +4 -4
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic.egg-info/PKG-INFO +1 -1
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic.egg-info/SOURCES.txt +2 -2
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/pyproject.toml +2 -2
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_json_rpc_client_integration.py +2 -2
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_xml_rpc_proxy_integration.py +2 -2
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/LICENSE +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/README.md +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/async_support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/caches/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/caches/dynamic.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/caches/persistent.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/client/_rpc_errors.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/context.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/converter.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/decorators.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/exceptions.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/hmcli.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/climate.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/data_point.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/climate.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/const.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/cover.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/data_point.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/definition.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/light.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/lock.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/siren.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/custom/valve.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/data_point.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/device.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/event.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/action.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/binary_sensor.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/button.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/data_point.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/number.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/select.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/sensor.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/switch.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/generic/text.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/binary_sensor.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/button.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/data_point.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/number.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/select.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/sensor.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/switch.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/hub/text.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/model/update.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/property_decorators.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/py.typed +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/get_serial.fn +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic/validator.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic.egg-info/dependency_links.txt +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic.egg-info/requires.txt +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic.egg-info/top_level.txt +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic_support/__init__.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/aiohomematic_support/client_local.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/setup.cfg +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_action.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_async_support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_binary_sensor.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_button.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_calculated_support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_central.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_central_pydevccu.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_climate.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_cover.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_decorator.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_device.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_dynamic_caches.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_entity.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_event.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_json_rpc.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_kwonly_lint.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_light.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_lock.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_logging_support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_number.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_select.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_sensor.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_siren.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_support.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_support_extra.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_switch.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_text.py +0 -0
- {aiohomematic-2025.10.4 → aiohomematic-2025.10.6}/tests/test_valve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.6
|
|
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,8 +171,8 @@ _IGNORED_PARAMETERS: Final[frozenset[TParameterName]] = frozenset(
|
|
|
171
171
|
"CLEAR_ERROR",
|
|
172
172
|
"CLEAR_WINDOW_OPEN_SYMBOL",
|
|
173
173
|
"CLOCK",
|
|
174
|
-
"CMD_RETL", #
|
|
175
|
-
"CMD_RETS", #
|
|
174
|
+
"CMD_RETL", # CUxD
|
|
175
|
+
"CMD_RETS", # CUxD
|
|
176
176
|
"CONTROL_DIFFERENTIAL_TEMPERATURE",
|
|
177
177
|
"DATE_TIME_UNKNOWN",
|
|
178
178
|
"DECISION_VALUE",
|
|
@@ -82,10 +82,10 @@ from aiohomematic.async_support import Looper, loop_check
|
|
|
82
82
|
from aiohomematic.caches.dynamic import CentralDataCache, DeviceDetailsCache
|
|
83
83
|
from aiohomematic.caches.persistent import DeviceDescriptionCache, ParamsetDescriptionCache
|
|
84
84
|
from aiohomematic.caches.visibility import ParameterVisibilityCache
|
|
85
|
-
from aiohomematic.central import
|
|
85
|
+
from aiohomematic.central import rpc_server as rpc
|
|
86
86
|
from aiohomematic.central.decorators import callback_backend_system, callback_event
|
|
87
|
-
from aiohomematic.client.json_rpc import
|
|
88
|
-
from aiohomematic.client.
|
|
87
|
+
from aiohomematic.client.json_rpc import AioJsonRpcAioHttpClient
|
|
88
|
+
from aiohomematic.client.rpc_proxy import AioXmlRpcProxy
|
|
89
89
|
from aiohomematic.const import (
|
|
90
90
|
CALLBACK_TYPE,
|
|
91
91
|
CATEGORIES,
|
|
@@ -98,6 +98,7 @@ from aiohomematic.const import (
|
|
|
98
98
|
DEFAULT_ENABLE_SYSVAR_SCAN,
|
|
99
99
|
DEFAULT_HM_MASTER_POLL_AFTER_SEND_INTERVALS,
|
|
100
100
|
DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS,
|
|
101
|
+
DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH,
|
|
101
102
|
DEFAULT_MAX_READ_WORKERS,
|
|
102
103
|
DEFAULT_PERIODIC_REFRESH_INTERVAL,
|
|
103
104
|
DEFAULT_PROGRAM_MARKERS,
|
|
@@ -112,7 +113,6 @@ from aiohomematic.const import (
|
|
|
112
113
|
DEVICE_FIRMWARE_DELIVERING_CHECK_INTERVAL,
|
|
113
114
|
DEVICE_FIRMWARE_UPDATING_CHECK_INTERVAL,
|
|
114
115
|
IGNORE_FOR_UN_IGNORE_PARAMETERS,
|
|
115
|
-
INTERFACES_REQUIRING_PERIODIC_REFRESH,
|
|
116
116
|
IP_ANY_V4,
|
|
117
117
|
LOCAL_HOST,
|
|
118
118
|
PORT_ANY,
|
|
@@ -137,6 +137,7 @@ from aiohomematic.const import (
|
|
|
137
137
|
Parameter,
|
|
138
138
|
ParamsetKey,
|
|
139
139
|
ProxyInitState,
|
|
140
|
+
RpcServerType,
|
|
140
141
|
SourceOfDeviceCreation,
|
|
141
142
|
SystemInformation,
|
|
142
143
|
)
|
|
@@ -180,7 +181,7 @@ _LOGGER_EVENT: Final = logging.getLogger(f"{__package__}.event")
|
|
|
180
181
|
|
|
181
182
|
# {central_name, central}
|
|
182
183
|
CENTRAL_INSTANCES: Final[dict[str, CentralUnit]] = {}
|
|
183
|
-
ConnectionProblemIssuer =
|
|
184
|
+
ConnectionProblemIssuer = AioJsonRpcAioHttpClient | AioXmlRpcProxy
|
|
184
185
|
|
|
185
186
|
INTERFACE_EVENT_SCHEMA = vol.Schema(
|
|
186
187
|
{
|
|
@@ -208,8 +209,8 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
208
209
|
self._url: Final = self._config.create_central_url()
|
|
209
210
|
self._model: str | None = None
|
|
210
211
|
self._looper = Looper()
|
|
211
|
-
self._xml_rpc_server:
|
|
212
|
-
self._json_rpc_client:
|
|
212
|
+
self._xml_rpc_server: rpc.XmlRpcServer | None = None
|
|
213
|
+
self._json_rpc_client: AioJsonRpcAioHttpClient | None = None
|
|
213
214
|
|
|
214
215
|
# Caches for the backend data
|
|
215
216
|
self._data_cache: Final = CentralDataCache(central=self)
|
|
@@ -248,9 +249,9 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
248
249
|
self._version: str | None = None
|
|
249
250
|
# store last event received datetime by interface_id
|
|
250
251
|
self._last_event_seen_for_interface: Final[dict[str, datetime]] = {}
|
|
251
|
-
self.
|
|
252
|
+
self._rpc_callback_ip: str = IP_ANY_V4
|
|
252
253
|
self._listen_ip_addr: str = IP_ANY_V4
|
|
253
|
-
self.
|
|
254
|
+
self._listen_port_xml_rpc: int = PORT_ANY
|
|
254
255
|
|
|
255
256
|
@property
|
|
256
257
|
def available(self) -> bool:
|
|
@@ -260,7 +261,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
260
261
|
@property
|
|
261
262
|
def callback_ip_addr(self) -> str:
|
|
262
263
|
"""Return the xml rpc server callback ip address."""
|
|
263
|
-
return self.
|
|
264
|
+
return self._rpc_callback_ip
|
|
264
265
|
|
|
265
266
|
@info_property(log_context=True)
|
|
266
267
|
def url(self) -> str:
|
|
@@ -327,7 +328,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
327
328
|
return all(client.is_callback_alive() for client in self._clients.values())
|
|
328
329
|
|
|
329
330
|
@property
|
|
330
|
-
def json_rpc_client(self) ->
|
|
331
|
+
def json_rpc_client(self) -> AioJsonRpcAioHttpClient:
|
|
331
332
|
"""Return the json rpc client."""
|
|
332
333
|
if not self._json_rpc_client:
|
|
333
334
|
self._json_rpc_client = self._config.create_json_rpc_client(central=self)
|
|
@@ -363,9 +364,9 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
363
364
|
return self._listen_ip_addr
|
|
364
365
|
|
|
365
366
|
@property
|
|
366
|
-
def
|
|
367
|
+
def listen_port_xml_rpc(self) -> int:
|
|
367
368
|
"""Return the xml rpc listening server port."""
|
|
368
|
-
return self.
|
|
369
|
+
return self._listen_port_xml_rpc
|
|
369
370
|
|
|
370
371
|
@property
|
|
371
372
|
def looper(self) -> Looper:
|
|
@@ -483,22 +484,22 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
483
484
|
if self._config.enabled_interface_configs and (
|
|
484
485
|
ip_addr := await self._identify_ip_addr(port=self._config.connection_check_port)
|
|
485
486
|
):
|
|
486
|
-
self.
|
|
487
|
+
self._rpc_callback_ip = ip_addr
|
|
487
488
|
self._listen_ip_addr = self._config.listen_ip_addr if self._config.listen_ip_addr else ip_addr
|
|
488
489
|
|
|
489
|
-
|
|
490
|
-
self._config.
|
|
491
|
-
if self._config.
|
|
492
|
-
else self._config.
|
|
490
|
+
port_xml_rpc: int = (
|
|
491
|
+
self._config.listen_port_xml_rpc
|
|
492
|
+
if self._config.listen_port_xml_rpc
|
|
493
|
+
else self._config.callback_port_xml_rpc or self._config.default_callback_port_xml_rpc
|
|
493
494
|
)
|
|
494
495
|
try:
|
|
495
496
|
if (
|
|
496
|
-
xml_rpc_server :=
|
|
497
|
-
if self._config.
|
|
497
|
+
xml_rpc_server := rpc.create_xml_rpc_server(ip_addr=self._listen_ip_addr, port=port_xml_rpc)
|
|
498
|
+
if self._config.enable_xml_rpc_server
|
|
498
499
|
else None
|
|
499
500
|
):
|
|
500
501
|
self._xml_rpc_server = xml_rpc_server
|
|
501
|
-
self.
|
|
502
|
+
self._listen_port_xml_rpc = xml_rpc_server.listen_port
|
|
502
503
|
self._xml_rpc_server.add_central(central=self)
|
|
503
504
|
except OSError as oserr:
|
|
504
505
|
self._state = CentralUnitState.STOPPED_BY_ERROR
|
|
@@ -514,7 +515,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
514
515
|
)
|
|
515
516
|
else:
|
|
516
517
|
self._clients_started = await self._start_clients()
|
|
517
|
-
if self._config.
|
|
518
|
+
if self._config.enable_xml_rpc_server:
|
|
518
519
|
self._start_scheduler()
|
|
519
520
|
|
|
520
521
|
self._state = CentralUnitState.RUNNING
|
|
@@ -1166,7 +1167,8 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
1166
1167
|
return tuple(
|
|
1167
1168
|
dev_desc
|
|
1168
1169
|
for dev_desc in device_descriptions
|
|
1169
|
-
if (dev_desc["ADDRESS"] if
|
|
1170
|
+
if (dev_desc["ADDRESS"] if not (parent_address := dev_desc.get("PARENT")) else parent_address)
|
|
1171
|
+
not in known_addresses
|
|
1170
1172
|
)
|
|
1171
1173
|
|
|
1172
1174
|
def _check_for_new_device_addresses(self, *, interface_id: str | None = None) -> Mapping[str, set[str]]:
|
|
@@ -1330,7 +1332,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
1330
1332
|
self._data_point_key_event_subscriptions[data_point.dpk] = []
|
|
1331
1333
|
self._data_point_key_event_subscriptions[data_point.dpk].append(data_point.event)
|
|
1332
1334
|
if (
|
|
1333
|
-
not data_point.channel.device.client.
|
|
1335
|
+
not data_point.channel.device.client.supports_rpc_callback
|
|
1334
1336
|
and data_point.state_path not in self._data_point_path_event_subscriptions
|
|
1335
1337
|
):
|
|
1336
1338
|
self._data_point_path_event_subscriptions[data_point.state_path] = data_point.dpk
|
|
@@ -1970,18 +1972,18 @@ class CentralConfig:
|
|
|
1970
1972
|
username: str,
|
|
1971
1973
|
client_session: ClientSession | None = None,
|
|
1972
1974
|
callback_host: str | None = None,
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
+
callback_port_xml_rpc: int | None = None,
|
|
1976
|
+
default_callback_port_xml_rpc: int = PORT_ANY,
|
|
1975
1977
|
delay_new_device_creation: bool = DEFAULT_DELAY_NEW_DEVICE_CREATION,
|
|
1976
1978
|
enable_device_firmware_check: bool = DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK,
|
|
1977
1979
|
enable_program_scan: bool = DEFAULT_ENABLE_PROGRAM_SCAN,
|
|
1978
1980
|
enable_sysvar_scan: bool = DEFAULT_ENABLE_SYSVAR_SCAN,
|
|
1979
1981
|
hm_master_poll_after_send_intervals: tuple[int, ...] = DEFAULT_HM_MASTER_POLL_AFTER_SEND_INTERVALS,
|
|
1980
1982
|
ignore_custom_device_definition_models: frozenset[str] = DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS,
|
|
1981
|
-
interfaces_requiring_periodic_refresh: frozenset[Interface] =
|
|
1983
|
+
interfaces_requiring_periodic_refresh: frozenset[Interface] = DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH,
|
|
1982
1984
|
json_port: int | None = None,
|
|
1983
1985
|
listen_ip_addr: str | None = None,
|
|
1984
|
-
|
|
1986
|
+
listen_port_xml_rpc: int | None = None,
|
|
1985
1987
|
max_read_workers: int = DEFAULT_MAX_READ_WORKERS,
|
|
1986
1988
|
periodic_refresh_interval: int = DEFAULT_PERIODIC_REFRESH_INTERVAL,
|
|
1987
1989
|
program_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_PROGRAM_MARKERS,
|
|
@@ -1996,11 +1998,14 @@ class CentralConfig:
|
|
|
1996
1998
|
) -> None:
|
|
1997
1999
|
"""Init the client config."""
|
|
1998
2000
|
self._interface_configs: Final = interface_configs
|
|
2001
|
+
self.requires_xml_rpc_server: Final = any(
|
|
2002
|
+
ic for ic in interface_configs if ic.rpc_server == RpcServerType.XML_RPC
|
|
2003
|
+
)
|
|
1999
2004
|
self.callback_host: Final = callback_host
|
|
2000
|
-
self.
|
|
2005
|
+
self.callback_port_xml_rpc: Final = callback_port_xml_rpc
|
|
2001
2006
|
self.central_id: Final = central_id
|
|
2002
2007
|
self.client_session: Final = client_session
|
|
2003
|
-
self.
|
|
2008
|
+
self.default_callback_port_xml_rpc: Final = default_callback_port_xml_rpc
|
|
2004
2009
|
self.delay_new_device_creation: Final = delay_new_device_creation
|
|
2005
2010
|
self.enable_device_firmware_check: Final = enable_device_firmware_check
|
|
2006
2011
|
self.enable_program_scan: Final = enable_program_scan
|
|
@@ -2011,7 +2016,7 @@ class CentralConfig:
|
|
|
2011
2016
|
self.interfaces_requiring_periodic_refresh: Final = frozenset(interfaces_requiring_periodic_refresh or ())
|
|
2012
2017
|
self.json_port: Final = json_port
|
|
2013
2018
|
self.listen_ip_addr: Final = listen_ip_addr
|
|
2014
|
-
self.
|
|
2019
|
+
self.listen_port_xml_rpc: Final = listen_port_xml_rpc
|
|
2015
2020
|
self.max_read_workers = max_read_workers
|
|
2016
2021
|
self.name: Final = name
|
|
2017
2022
|
self.password: Final = password
|
|
@@ -2028,9 +2033,9 @@ class CentralConfig:
|
|
|
2028
2033
|
self.verify_tls: Final = verify_tls
|
|
2029
2034
|
|
|
2030
2035
|
@property
|
|
2031
|
-
def
|
|
2036
|
+
def enable_xml_rpc_server(self) -> bool:
|
|
2032
2037
|
"""Return if server and connection checker should be started."""
|
|
2033
|
-
return self.start_direct is False
|
|
2038
|
+
return self.requires_xml_rpc_server and self.start_direct is False
|
|
2034
2039
|
|
|
2035
2040
|
@property
|
|
2036
2041
|
def load_un_ignore(self) -> bool:
|
|
@@ -2065,7 +2070,7 @@ class CentralConfig:
|
|
|
2065
2070
|
password=self.password,
|
|
2066
2071
|
storage_folder=self.storage_folder,
|
|
2067
2072
|
callback_host=self.callback_host,
|
|
2068
|
-
|
|
2073
|
+
callback_port_xml_rpc=self.callback_port_xml_rpc,
|
|
2069
2074
|
json_port=self.json_port,
|
|
2070
2075
|
interface_configs=self._interface_configs,
|
|
2071
2076
|
):
|
|
@@ -2090,9 +2095,9 @@ class CentralConfig:
|
|
|
2090
2095
|
url = f"{url}:{self.json_port}"
|
|
2091
2096
|
return f"{url}"
|
|
2092
2097
|
|
|
2093
|
-
def create_json_rpc_client(self, *, central: CentralUnit) ->
|
|
2098
|
+
def create_json_rpc_client(self, *, central: CentralUnit) -> AioJsonRpcAioHttpClient:
|
|
2094
2099
|
"""Create a json rpc client."""
|
|
2095
|
-
return
|
|
2100
|
+
return AioJsonRpcAioHttpClient(
|
|
2096
2101
|
username=self.username,
|
|
2097
2102
|
password=self.password,
|
|
2098
2103
|
device_url=central.url,
|
|
@@ -2109,38 +2114,38 @@ class CentralConnectionState:
|
|
|
2109
2114
|
def __init__(self) -> None:
|
|
2110
2115
|
"""Init the CentralConnectionStatus."""
|
|
2111
2116
|
self._json_issues: Final[list[str]] = []
|
|
2112
|
-
self.
|
|
2117
|
+
self._rpc_proxy_issues: Final[list[str]] = []
|
|
2113
2118
|
|
|
2114
2119
|
def add_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
|
|
2115
2120
|
"""Add issue to collection."""
|
|
2116
|
-
if isinstance(issuer,
|
|
2121
|
+
if isinstance(issuer, AioJsonRpcAioHttpClient) and iid not in self._json_issues:
|
|
2117
2122
|
self._json_issues.append(iid)
|
|
2118
2123
|
_LOGGER.debug("add_issue: add issue [%s] for JsonRpcAioHttpClient", iid)
|
|
2119
2124
|
return True
|
|
2120
|
-
if isinstance(issuer,
|
|
2121
|
-
self.
|
|
2125
|
+
if isinstance(issuer, AioXmlRpcProxy) and iid not in self._rpc_proxy_issues:
|
|
2126
|
+
self._rpc_proxy_issues.append(iid)
|
|
2122
2127
|
_LOGGER.debug("add_issue: add issue [%s] for %s", iid, issuer.interface_id)
|
|
2123
2128
|
return True
|
|
2124
2129
|
return False
|
|
2125
2130
|
|
|
2126
2131
|
def remove_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
|
|
2127
2132
|
"""Add issue to collection."""
|
|
2128
|
-
if isinstance(issuer,
|
|
2133
|
+
if isinstance(issuer, AioJsonRpcAioHttpClient) and iid in self._json_issues:
|
|
2129
2134
|
self._json_issues.remove(iid)
|
|
2130
2135
|
_LOGGER.debug("remove_issue: removing issue [%s] for JsonRpcAioHttpClient", iid)
|
|
2131
2136
|
return True
|
|
2132
|
-
if isinstance(issuer,
|
|
2133
|
-
self.
|
|
2137
|
+
if isinstance(issuer, AioXmlRpcProxy) and issuer.interface_id in self._rpc_proxy_issues:
|
|
2138
|
+
self._rpc_proxy_issues.remove(iid)
|
|
2134
2139
|
_LOGGER.debug("remove_issue: removing issue [%s] for %s", iid, issuer.interface_id)
|
|
2135
2140
|
return True
|
|
2136
2141
|
return False
|
|
2137
2142
|
|
|
2138
2143
|
def has_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
|
|
2139
2144
|
"""Add issue to collection."""
|
|
2140
|
-
if isinstance(issuer,
|
|
2145
|
+
if isinstance(issuer, AioJsonRpcAioHttpClient):
|
|
2141
2146
|
return iid in self._json_issues
|
|
2142
|
-
if isinstance(issuer,
|
|
2143
|
-
return iid in self.
|
|
2147
|
+
if isinstance(issuer, (AioXmlRpcProxy)):
|
|
2148
|
+
return iid in self._rpc_proxy_issues
|
|
2144
2149
|
|
|
2145
2150
|
def handle_exception_log(
|
|
2146
2151
|
self,
|
|
@@ -12,7 +12,7 @@ import logging
|
|
|
12
12
|
from typing import Any, Final, cast
|
|
13
13
|
|
|
14
14
|
from aiohomematic import central as hmcu, client as hmcl
|
|
15
|
-
from aiohomematic.central import
|
|
15
|
+
from aiohomematic.central import rpc_server as rpc
|
|
16
16
|
from aiohomematic.const import BackendSystemEvent
|
|
17
17
|
from aiohomematic.exceptions import AioHomematicException
|
|
18
18
|
from aiohomematic.support import extract_exc_args
|
|
@@ -48,7 +48,7 @@ def callback_backend_system(system_event: BackendSystemEvent) -> Callable:
|
|
|
48
48
|
central: hmcu.CentralUnit | None = None
|
|
49
49
|
if isinstance(unit, hmcu.CentralUnit):
|
|
50
50
|
central = unit
|
|
51
|
-
if central is None and isinstance(unit,
|
|
51
|
+
if central is None and isinstance(unit, rpc.RPCFunctions):
|
|
52
52
|
central = unit.get_central(interface_id=str(args[1]))
|
|
53
53
|
if central:
|
|
54
54
|
central.looper.create_task(
|
|
@@ -25,14 +25,14 @@ _LOGGER: Final = logging.getLogger(__name__)
|
|
|
25
25
|
|
|
26
26
|
# pylint: disable=invalid-name
|
|
27
27
|
class RPCFunctions:
|
|
28
|
-
"""The
|
|
28
|
+
"""The RPC functions the backend will expect."""
|
|
29
29
|
|
|
30
|
-
# Disable kw-only linter
|
|
30
|
+
# Disable kw-only linter
|
|
31
31
|
__kwonly_check__ = False
|
|
32
32
|
|
|
33
|
-
def __init__(self, *,
|
|
33
|
+
def __init__(self, *, rpc_server: RpcServer) -> None:
|
|
34
34
|
"""Init RPCFunctions."""
|
|
35
|
-
self.
|
|
35
|
+
self._rpc_server: Final = rpc_server
|
|
36
36
|
|
|
37
37
|
def event(self, interface_id: str, channel_address: str, parameter: str, value: Any, /) -> None:
|
|
38
38
|
"""If a device emits some sort event, we will handle it here."""
|
|
@@ -50,13 +50,13 @@ class RPCFunctions:
|
|
|
50
50
|
@callback_backend_system(system_event=BackendSystemEvent.ERROR)
|
|
51
51
|
def error(self, interface_id: str, error_code: str, msg: str, /) -> None:
|
|
52
52
|
"""When some error occurs the backend will send its error message here."""
|
|
53
|
-
# Structured boundary log (warning level).
|
|
53
|
+
# Structured boundary log (warning level). RPC server received error notification.
|
|
54
54
|
try:
|
|
55
55
|
raise RuntimeError(str(msg))
|
|
56
56
|
except RuntimeError as err:
|
|
57
57
|
log_boundary_error(
|
|
58
58
|
logger=_LOGGER,
|
|
59
|
-
boundary="
|
|
59
|
+
boundary="rpc-server",
|
|
60
60
|
action="error",
|
|
61
61
|
err=err,
|
|
62
62
|
level=logging.WARNING,
|
|
@@ -137,7 +137,7 @@ class RPCFunctions:
|
|
|
137
137
|
|
|
138
138
|
def get_central(self, *, interface_id: str) -> hmcu.CentralUnit | None:
|
|
139
139
|
"""Return the central by interface_id."""
|
|
140
|
-
return self.
|
|
140
|
+
return self._rpc_server.get_central(interface_id=interface_id)
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
# Restrict to specific paths.
|
|
@@ -150,7 +150,7 @@ class RequestHandler(SimpleXMLRPCRequestHandler):
|
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
class
|
|
153
|
+
class HomematicXMLRPCServer(SimpleXMLRPCServer):
|
|
154
154
|
"""
|
|
155
155
|
Simple XML-RPC server.
|
|
156
156
|
|
|
@@ -171,8 +171,8 @@ class AioHomematicXMLRPCServer(SimpleXMLRPCServer):
|
|
|
171
171
|
return SimpleXMLRPCServer.system_listMethods(self)
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
class
|
|
175
|
-
"""
|
|
174
|
+
class RpcServer(threading.Thread):
|
|
175
|
+
"""RPC server thread to handle messages from the backend."""
|
|
176
176
|
|
|
177
177
|
_initialized: bool = False
|
|
178
178
|
_instances: Final[dict[tuple[str, int], XmlRpcServer]] = {}
|
|
@@ -190,46 +190,30 @@ class XmlRpcServer(threading.Thread):
|
|
|
190
190
|
self._listen_ip_addr: Final = ip_addr
|
|
191
191
|
self._listen_port: Final[int] = find_free_port() if port == PORT_ANY else port
|
|
192
192
|
self._address: Final[tuple[str, int]] = (ip_addr, self._listen_port)
|
|
193
|
-
|
|
194
|
-
threading.Thread.__init__(self, name=f"XmlRpcServer {ip_addr}:{self._listen_port}")
|
|
195
|
-
self._simple_xml_rpc_server = AioHomematicXMLRPCServer(
|
|
196
|
-
addr=self._address,
|
|
197
|
-
requestHandler=RequestHandler,
|
|
198
|
-
logRequests=False,
|
|
199
|
-
allow_none=True,
|
|
200
|
-
)
|
|
201
|
-
self._simple_xml_rpc_server.register_introspection_functions()
|
|
202
|
-
self._simple_xml_rpc_server.register_multicall_functions()
|
|
203
|
-
self._simple_xml_rpc_server.register_instance(RPCFunctions(xml_rpc_server=self), allow_dotted_names=True)
|
|
193
|
+
threading.Thread.__init__(self, name=f"RpcServer {ip_addr}:{self._listen_port}")
|
|
204
194
|
self._centrals: Final[dict[str, hmcu.CentralUnit]] = {}
|
|
205
|
-
|
|
206
|
-
def __new__(cls, ip_addr: str, port: int) -> XmlRpcServer: # noqa: PYI034 # kwonly: disable
|
|
207
|
-
"""Create new XmlRPC server."""
|
|
208
|
-
if (xml_rpc := cls._instances.get((ip_addr, port))) is None:
|
|
209
|
-
_LOGGER.debug("Creating XmlRpc server")
|
|
210
|
-
return super().__new__(cls)
|
|
211
|
-
return xml_rpc
|
|
195
|
+
self._simple_rpc_server: SimpleXMLRPCServer
|
|
212
196
|
|
|
213
197
|
def run(self) -> None:
|
|
214
|
-
"""Run the
|
|
198
|
+
"""Run the RPC-Server thread."""
|
|
215
199
|
_LOGGER.debug(
|
|
216
|
-
"RUN: Starting
|
|
200
|
+
"RUN: Starting RPC-Server listening on %s:%i",
|
|
217
201
|
self._listen_ip_addr,
|
|
218
202
|
self._listen_port,
|
|
219
203
|
)
|
|
220
|
-
if self.
|
|
221
|
-
self.
|
|
204
|
+
if self._simple_rpc_server:
|
|
205
|
+
self._simple_rpc_server.serve_forever()
|
|
222
206
|
|
|
223
207
|
def stop(self) -> None:
|
|
224
|
-
"""Stop the
|
|
225
|
-
_LOGGER.debug("STOP: Shutting down
|
|
226
|
-
self.
|
|
227
|
-
_LOGGER.debug("STOP: Stopping
|
|
228
|
-
self.
|
|
208
|
+
"""Stop the RPC-Server."""
|
|
209
|
+
_LOGGER.debug("STOP: Shutting down RPC-Server")
|
|
210
|
+
self._simple_rpc_server.shutdown()
|
|
211
|
+
_LOGGER.debug("STOP: Stopping RPC-Server")
|
|
212
|
+
self._simple_rpc_server.server_close()
|
|
229
213
|
# Ensure the server thread has actually terminated to avoid slow teardown
|
|
230
214
|
with contextlib.suppress(RuntimeError):
|
|
231
215
|
self.join(timeout=1.0)
|
|
232
|
-
_LOGGER.debug("STOP:
|
|
216
|
+
_LOGGER.debug("STOP: RPC-Server stopped")
|
|
233
217
|
if self._address in self._instances:
|
|
234
218
|
del self._instances[self._address]
|
|
235
219
|
|
|
@@ -249,12 +233,12 @@ class XmlRpcServer(threading.Thread):
|
|
|
249
233
|
return self._started.is_set() is True # type: ignore[attr-defined]
|
|
250
234
|
|
|
251
235
|
def add_central(self, *, central: hmcu.CentralUnit) -> None:
|
|
252
|
-
"""Register a central in the
|
|
236
|
+
"""Register a central in the RPC-Server."""
|
|
253
237
|
if not self._centrals.get(central.name):
|
|
254
238
|
self._centrals[central.name] = central
|
|
255
239
|
|
|
256
240
|
def remove_central(self, *, central: hmcu.CentralUnit) -> None:
|
|
257
|
-
"""Unregister a central from
|
|
241
|
+
"""Unregister a central from RPC-Server."""
|
|
258
242
|
if self._centrals.get(central.name):
|
|
259
243
|
del self._centrals[central.name]
|
|
260
244
|
|
|
@@ -271,14 +255,45 @@ class XmlRpcServer(threading.Thread):
|
|
|
271
255
|
return len(self._centrals) == 0
|
|
272
256
|
|
|
273
257
|
|
|
258
|
+
class XmlRpcServer(RpcServer):
|
|
259
|
+
"""XML-RPC server thread to handle messages from the backend."""
|
|
260
|
+
|
|
261
|
+
def __init__(
|
|
262
|
+
self,
|
|
263
|
+
*,
|
|
264
|
+
ip_addr: str,
|
|
265
|
+
port: int,
|
|
266
|
+
) -> None:
|
|
267
|
+
"""Init XmlRPC server."""
|
|
268
|
+
|
|
269
|
+
super().__init__(ip_addr=ip_addr, port=port)
|
|
270
|
+
self._instances[self._address] = self
|
|
271
|
+
self._simple_rpc_server = HomematicXMLRPCServer(
|
|
272
|
+
addr=self._address,
|
|
273
|
+
requestHandler=RequestHandler,
|
|
274
|
+
logRequests=False,
|
|
275
|
+
allow_none=True,
|
|
276
|
+
)
|
|
277
|
+
self._simple_rpc_server.register_introspection_functions()
|
|
278
|
+
self._simple_rpc_server.register_multicall_functions()
|
|
279
|
+
self._simple_rpc_server.register_instance(RPCFunctions(rpc_server=self), allow_dotted_names=True)
|
|
280
|
+
|
|
281
|
+
def __new__(cls, ip_addr: str, port: int) -> XmlRpcServer: # noqa: PYI034 # kwonly: disable
|
|
282
|
+
"""Create new RPC server."""
|
|
283
|
+
if (rpc := cls._instances.get((ip_addr, port))) is None:
|
|
284
|
+
_LOGGER.debug("Creating XmlRpc server")
|
|
285
|
+
return super().__new__(cls)
|
|
286
|
+
return rpc
|
|
287
|
+
|
|
288
|
+
|
|
274
289
|
def create_xml_rpc_server(*, ip_addr: str = IP_ANY_V4, port: int = PORT_ANY) -> XmlRpcServer:
|
|
275
|
-
"""Register the
|
|
276
|
-
|
|
277
|
-
if not
|
|
278
|
-
|
|
290
|
+
"""Register the rpc server."""
|
|
291
|
+
rpc = XmlRpcServer(ip_addr=ip_addr, port=port)
|
|
292
|
+
if not rpc.started:
|
|
293
|
+
rpc.start()
|
|
279
294
|
_LOGGER.debug(
|
|
280
295
|
"CREATE_XML_RPC_SERVER: Starting XmlRPC-Server listening on %s:%i",
|
|
281
|
-
|
|
282
|
-
|
|
296
|
+
rpc.listen_ip_addr,
|
|
297
|
+
rpc.listen_port,
|
|
283
298
|
)
|
|
284
|
-
return
|
|
299
|
+
return rpc
|