aiohomematic 2025.10.5__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.5 → aiohomematic-2025.10.6}/PKG-INFO +1 -1
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/caches/visibility.py +2 -2
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/central/__init__.py +49 -45
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/central/decorators.py +2 -2
- aiohomematic-2025.10.5/aiohomematic/central/xml_rpc_server.py → aiohomematic-2025.10.6/aiohomematic/central/rpc_server.py +62 -47
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/client/__init__.py +118 -78
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/client/json_rpc.py +1 -1
- aiohomematic-2025.10.5/aiohomematic/client/xml_rpc.py → aiohomematic-2025.10.6/aiohomematic/client/rpc_proxy.py +78 -43
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/const.py +31 -4
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/switch.py +1 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/support.py +3 -3
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic.egg-info/PKG-INFO +1 -1
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic.egg-info/SOURCES.txt +2 -2
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/pyproject.toml +2 -2
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_json_rpc_client_integration.py +2 -2
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_xml_rpc_proxy_integration.py +2 -2
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/LICENSE +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/README.md +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/async_support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/caches/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/caches/dynamic.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/caches/persistent.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/client/_rpc_errors.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/context.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/converter.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/decorators.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/exceptions.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/hmcli.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/climate.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/data_point.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/calculated/support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/climate.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/const.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/cover.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/data_point.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/definition.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/light.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/lock.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/siren.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/custom/valve.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/data_point.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/device.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/event.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/action.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/binary_sensor.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/button.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/data_point.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/number.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/select.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/sensor.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/switch.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/generic/text.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/binary_sensor.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/button.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/data_point.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/number.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/select.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/sensor.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/switch.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/hub/text.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/model/update.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/property_decorators.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/py.typed +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/get_serial.fn +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic/validator.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic.egg-info/dependency_links.txt +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic.egg-info/requires.txt +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic.egg-info/top_level.txt +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic_support/__init__.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/aiohomematic_support/client_local.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/setup.cfg +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_action.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_async_support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_binary_sensor.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_button.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_calculated_support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_central.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_central_pydevccu.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_climate.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_cover.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_decorator.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_device.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_dynamic_caches.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_entity.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_event.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_json_rpc.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_kwonly_lint.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_light.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_lock.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_logging_support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_number.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_select.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_sensor.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_siren.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_support.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_support_extra.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_switch.py +0 -0
- {aiohomematic-2025.10.5 → aiohomematic-2025.10.6}/tests/test_text.py +0 -0
- {aiohomematic-2025.10.5 → 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
|
|
@@ -1331,7 +1332,7 @@ class CentralUnit(LogContextMixin, PayloadMixin):
|
|
|
1331
1332
|
self._data_point_key_event_subscriptions[data_point.dpk] = []
|
|
1332
1333
|
self._data_point_key_event_subscriptions[data_point.dpk].append(data_point.event)
|
|
1333
1334
|
if (
|
|
1334
|
-
not data_point.channel.device.client.
|
|
1335
|
+
not data_point.channel.device.client.supports_rpc_callback
|
|
1335
1336
|
and data_point.state_path not in self._data_point_path_event_subscriptions
|
|
1336
1337
|
):
|
|
1337
1338
|
self._data_point_path_event_subscriptions[data_point.state_path] = data_point.dpk
|
|
@@ -1971,18 +1972,18 @@ class CentralConfig:
|
|
|
1971
1972
|
username: str,
|
|
1972
1973
|
client_session: ClientSession | None = None,
|
|
1973
1974
|
callback_host: str | None = None,
|
|
1974
|
-
|
|
1975
|
-
|
|
1975
|
+
callback_port_xml_rpc: int | None = None,
|
|
1976
|
+
default_callback_port_xml_rpc: int = PORT_ANY,
|
|
1976
1977
|
delay_new_device_creation: bool = DEFAULT_DELAY_NEW_DEVICE_CREATION,
|
|
1977
1978
|
enable_device_firmware_check: bool = DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK,
|
|
1978
1979
|
enable_program_scan: bool = DEFAULT_ENABLE_PROGRAM_SCAN,
|
|
1979
1980
|
enable_sysvar_scan: bool = DEFAULT_ENABLE_SYSVAR_SCAN,
|
|
1980
1981
|
hm_master_poll_after_send_intervals: tuple[int, ...] = DEFAULT_HM_MASTER_POLL_AFTER_SEND_INTERVALS,
|
|
1981
1982
|
ignore_custom_device_definition_models: frozenset[str] = DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS,
|
|
1982
|
-
interfaces_requiring_periodic_refresh: frozenset[Interface] =
|
|
1983
|
+
interfaces_requiring_periodic_refresh: frozenset[Interface] = DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH,
|
|
1983
1984
|
json_port: int | None = None,
|
|
1984
1985
|
listen_ip_addr: str | None = None,
|
|
1985
|
-
|
|
1986
|
+
listen_port_xml_rpc: int | None = None,
|
|
1986
1987
|
max_read_workers: int = DEFAULT_MAX_READ_WORKERS,
|
|
1987
1988
|
periodic_refresh_interval: int = DEFAULT_PERIODIC_REFRESH_INTERVAL,
|
|
1988
1989
|
program_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_PROGRAM_MARKERS,
|
|
@@ -1997,11 +1998,14 @@ class CentralConfig:
|
|
|
1997
1998
|
) -> None:
|
|
1998
1999
|
"""Init the client config."""
|
|
1999
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
|
+
)
|
|
2000
2004
|
self.callback_host: Final = callback_host
|
|
2001
|
-
self.
|
|
2005
|
+
self.callback_port_xml_rpc: Final = callback_port_xml_rpc
|
|
2002
2006
|
self.central_id: Final = central_id
|
|
2003
2007
|
self.client_session: Final = client_session
|
|
2004
|
-
self.
|
|
2008
|
+
self.default_callback_port_xml_rpc: Final = default_callback_port_xml_rpc
|
|
2005
2009
|
self.delay_new_device_creation: Final = delay_new_device_creation
|
|
2006
2010
|
self.enable_device_firmware_check: Final = enable_device_firmware_check
|
|
2007
2011
|
self.enable_program_scan: Final = enable_program_scan
|
|
@@ -2012,7 +2016,7 @@ class CentralConfig:
|
|
|
2012
2016
|
self.interfaces_requiring_periodic_refresh: Final = frozenset(interfaces_requiring_periodic_refresh or ())
|
|
2013
2017
|
self.json_port: Final = json_port
|
|
2014
2018
|
self.listen_ip_addr: Final = listen_ip_addr
|
|
2015
|
-
self.
|
|
2019
|
+
self.listen_port_xml_rpc: Final = listen_port_xml_rpc
|
|
2016
2020
|
self.max_read_workers = max_read_workers
|
|
2017
2021
|
self.name: Final = name
|
|
2018
2022
|
self.password: Final = password
|
|
@@ -2029,9 +2033,9 @@ class CentralConfig:
|
|
|
2029
2033
|
self.verify_tls: Final = verify_tls
|
|
2030
2034
|
|
|
2031
2035
|
@property
|
|
2032
|
-
def
|
|
2036
|
+
def enable_xml_rpc_server(self) -> bool:
|
|
2033
2037
|
"""Return if server and connection checker should be started."""
|
|
2034
|
-
return self.start_direct is False
|
|
2038
|
+
return self.requires_xml_rpc_server and self.start_direct is False
|
|
2035
2039
|
|
|
2036
2040
|
@property
|
|
2037
2041
|
def load_un_ignore(self) -> bool:
|
|
@@ -2066,7 +2070,7 @@ class CentralConfig:
|
|
|
2066
2070
|
password=self.password,
|
|
2067
2071
|
storage_folder=self.storage_folder,
|
|
2068
2072
|
callback_host=self.callback_host,
|
|
2069
|
-
|
|
2073
|
+
callback_port_xml_rpc=self.callback_port_xml_rpc,
|
|
2070
2074
|
json_port=self.json_port,
|
|
2071
2075
|
interface_configs=self._interface_configs,
|
|
2072
2076
|
):
|
|
@@ -2091,9 +2095,9 @@ class CentralConfig:
|
|
|
2091
2095
|
url = f"{url}:{self.json_port}"
|
|
2092
2096
|
return f"{url}"
|
|
2093
2097
|
|
|
2094
|
-
def create_json_rpc_client(self, *, central: CentralUnit) ->
|
|
2098
|
+
def create_json_rpc_client(self, *, central: CentralUnit) -> AioJsonRpcAioHttpClient:
|
|
2095
2099
|
"""Create a json rpc client."""
|
|
2096
|
-
return
|
|
2100
|
+
return AioJsonRpcAioHttpClient(
|
|
2097
2101
|
username=self.username,
|
|
2098
2102
|
password=self.password,
|
|
2099
2103
|
device_url=central.url,
|
|
@@ -2110,38 +2114,38 @@ class CentralConnectionState:
|
|
|
2110
2114
|
def __init__(self) -> None:
|
|
2111
2115
|
"""Init the CentralConnectionStatus."""
|
|
2112
2116
|
self._json_issues: Final[list[str]] = []
|
|
2113
|
-
self.
|
|
2117
|
+
self._rpc_proxy_issues: Final[list[str]] = []
|
|
2114
2118
|
|
|
2115
2119
|
def add_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
|
|
2116
2120
|
"""Add issue to collection."""
|
|
2117
|
-
if isinstance(issuer,
|
|
2121
|
+
if isinstance(issuer, AioJsonRpcAioHttpClient) and iid not in self._json_issues:
|
|
2118
2122
|
self._json_issues.append(iid)
|
|
2119
2123
|
_LOGGER.debug("add_issue: add issue [%s] for JsonRpcAioHttpClient", iid)
|
|
2120
2124
|
return True
|
|
2121
|
-
if isinstance(issuer,
|
|
2122
|
-
self.
|
|
2125
|
+
if isinstance(issuer, AioXmlRpcProxy) and iid not in self._rpc_proxy_issues:
|
|
2126
|
+
self._rpc_proxy_issues.append(iid)
|
|
2123
2127
|
_LOGGER.debug("add_issue: add issue [%s] for %s", iid, issuer.interface_id)
|
|
2124
2128
|
return True
|
|
2125
2129
|
return False
|
|
2126
2130
|
|
|
2127
2131
|
def remove_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
|
|
2128
2132
|
"""Add issue to collection."""
|
|
2129
|
-
if isinstance(issuer,
|
|
2133
|
+
if isinstance(issuer, AioJsonRpcAioHttpClient) and iid in self._json_issues:
|
|
2130
2134
|
self._json_issues.remove(iid)
|
|
2131
2135
|
_LOGGER.debug("remove_issue: removing issue [%s] for JsonRpcAioHttpClient", iid)
|
|
2132
2136
|
return True
|
|
2133
|
-
if isinstance(issuer,
|
|
2134
|
-
self.
|
|
2137
|
+
if isinstance(issuer, AioXmlRpcProxy) and issuer.interface_id in self._rpc_proxy_issues:
|
|
2138
|
+
self._rpc_proxy_issues.remove(iid)
|
|
2135
2139
|
_LOGGER.debug("remove_issue: removing issue [%s] for %s", iid, issuer.interface_id)
|
|
2136
2140
|
return True
|
|
2137
2141
|
return False
|
|
2138
2142
|
|
|
2139
2143
|
def has_issue(self, *, issuer: ConnectionProblemIssuer, iid: str) -> bool:
|
|
2140
2144
|
"""Add issue to collection."""
|
|
2141
|
-
if isinstance(issuer,
|
|
2145
|
+
if isinstance(issuer, AioJsonRpcAioHttpClient):
|
|
2142
2146
|
return iid in self._json_issues
|
|
2143
|
-
if isinstance(issuer,
|
|
2144
|
-
return iid in self.
|
|
2147
|
+
if isinstance(issuer, (AioXmlRpcProxy)):
|
|
2148
|
+
return iid in self._rpc_proxy_issues
|
|
2145
2149
|
|
|
2146
2150
|
def handle_exception_log(
|
|
2147
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
|