aiohomematic 2026.1.29__py3-none-any.whl
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.
- aiohomematic/__init__.py +110 -0
- aiohomematic/_log_context_protocol.py +29 -0
- aiohomematic/api.py +410 -0
- aiohomematic/async_support.py +250 -0
- aiohomematic/backend_detection.py +462 -0
- aiohomematic/central/__init__.py +103 -0
- aiohomematic/central/async_rpc_server.py +760 -0
- aiohomematic/central/central_unit.py +1152 -0
- aiohomematic/central/config.py +463 -0
- aiohomematic/central/config_builder.py +772 -0
- aiohomematic/central/connection_state.py +160 -0
- aiohomematic/central/coordinators/__init__.py +38 -0
- aiohomematic/central/coordinators/cache.py +414 -0
- aiohomematic/central/coordinators/client.py +480 -0
- aiohomematic/central/coordinators/connection_recovery.py +1141 -0
- aiohomematic/central/coordinators/device.py +1166 -0
- aiohomematic/central/coordinators/event.py +514 -0
- aiohomematic/central/coordinators/hub.py +532 -0
- aiohomematic/central/decorators.py +184 -0
- aiohomematic/central/device_registry.py +229 -0
- aiohomematic/central/events/__init__.py +104 -0
- aiohomematic/central/events/bus.py +1392 -0
- aiohomematic/central/events/integration.py +424 -0
- aiohomematic/central/events/types.py +194 -0
- aiohomematic/central/health.py +762 -0
- aiohomematic/central/rpc_server.py +353 -0
- aiohomematic/central/scheduler.py +794 -0
- aiohomematic/central/state_machine.py +391 -0
- aiohomematic/client/__init__.py +203 -0
- aiohomematic/client/_rpc_errors.py +187 -0
- aiohomematic/client/backends/__init__.py +48 -0
- aiohomematic/client/backends/base.py +335 -0
- aiohomematic/client/backends/capabilities.py +138 -0
- aiohomematic/client/backends/ccu.py +487 -0
- aiohomematic/client/backends/factory.py +116 -0
- aiohomematic/client/backends/homegear.py +294 -0
- aiohomematic/client/backends/json_ccu.py +252 -0
- aiohomematic/client/backends/protocol.py +316 -0
- aiohomematic/client/ccu.py +1857 -0
- aiohomematic/client/circuit_breaker.py +459 -0
- aiohomematic/client/config.py +64 -0
- aiohomematic/client/handlers/__init__.py +40 -0
- aiohomematic/client/handlers/backup.py +157 -0
- aiohomematic/client/handlers/base.py +79 -0
- aiohomematic/client/handlers/device_ops.py +1085 -0
- aiohomematic/client/handlers/firmware.py +144 -0
- aiohomematic/client/handlers/link_mgmt.py +199 -0
- aiohomematic/client/handlers/metadata.py +436 -0
- aiohomematic/client/handlers/programs.py +144 -0
- aiohomematic/client/handlers/sysvars.py +100 -0
- aiohomematic/client/interface_client.py +1304 -0
- aiohomematic/client/json_rpc.py +2068 -0
- aiohomematic/client/request_coalescer.py +282 -0
- aiohomematic/client/rpc_proxy.py +629 -0
- aiohomematic/client/state_machine.py +324 -0
- aiohomematic/const.py +2207 -0
- aiohomematic/context.py +275 -0
- aiohomematic/converter.py +270 -0
- aiohomematic/decorators.py +390 -0
- aiohomematic/exceptions.py +185 -0
- aiohomematic/hmcli.py +997 -0
- aiohomematic/i18n.py +193 -0
- aiohomematic/interfaces/__init__.py +407 -0
- aiohomematic/interfaces/central.py +1067 -0
- aiohomematic/interfaces/client.py +1096 -0
- aiohomematic/interfaces/coordinators.py +63 -0
- aiohomematic/interfaces/model.py +1921 -0
- aiohomematic/interfaces/operations.py +217 -0
- aiohomematic/logging_context.py +134 -0
- aiohomematic/metrics/__init__.py +125 -0
- aiohomematic/metrics/_protocols.py +140 -0
- aiohomematic/metrics/aggregator.py +534 -0
- aiohomematic/metrics/dataclasses.py +489 -0
- aiohomematic/metrics/emitter.py +292 -0
- aiohomematic/metrics/events.py +183 -0
- aiohomematic/metrics/keys.py +300 -0
- aiohomematic/metrics/observer.py +563 -0
- aiohomematic/metrics/stats.py +172 -0
- aiohomematic/model/__init__.py +189 -0
- aiohomematic/model/availability.py +65 -0
- aiohomematic/model/calculated/__init__.py +89 -0
- aiohomematic/model/calculated/climate.py +276 -0
- aiohomematic/model/calculated/data_point.py +315 -0
- aiohomematic/model/calculated/field.py +147 -0
- aiohomematic/model/calculated/operating_voltage_level.py +286 -0
- aiohomematic/model/calculated/support.py +232 -0
- aiohomematic/model/custom/__init__.py +214 -0
- aiohomematic/model/custom/capabilities/__init__.py +67 -0
- aiohomematic/model/custom/capabilities/climate.py +41 -0
- aiohomematic/model/custom/capabilities/light.py +87 -0
- aiohomematic/model/custom/capabilities/lock.py +44 -0
- aiohomematic/model/custom/capabilities/siren.py +63 -0
- aiohomematic/model/custom/climate.py +1130 -0
- aiohomematic/model/custom/cover.py +722 -0
- aiohomematic/model/custom/data_point.py +360 -0
- aiohomematic/model/custom/definition.py +300 -0
- aiohomematic/model/custom/field.py +89 -0
- aiohomematic/model/custom/light.py +1174 -0
- aiohomematic/model/custom/lock.py +322 -0
- aiohomematic/model/custom/mixins.py +445 -0
- aiohomematic/model/custom/profile.py +945 -0
- aiohomematic/model/custom/registry.py +251 -0
- aiohomematic/model/custom/siren.py +462 -0
- aiohomematic/model/custom/switch.py +195 -0
- aiohomematic/model/custom/text_display.py +289 -0
- aiohomematic/model/custom/valve.py +78 -0
- aiohomematic/model/data_point.py +1416 -0
- aiohomematic/model/device.py +1840 -0
- aiohomematic/model/event.py +216 -0
- aiohomematic/model/generic/__init__.py +327 -0
- aiohomematic/model/generic/action.py +40 -0
- aiohomematic/model/generic/action_select.py +62 -0
- aiohomematic/model/generic/binary_sensor.py +30 -0
- aiohomematic/model/generic/button.py +31 -0
- aiohomematic/model/generic/data_point.py +177 -0
- aiohomematic/model/generic/dummy.py +150 -0
- aiohomematic/model/generic/number.py +76 -0
- aiohomematic/model/generic/select.py +56 -0
- aiohomematic/model/generic/sensor.py +76 -0
- aiohomematic/model/generic/switch.py +54 -0
- aiohomematic/model/generic/text.py +33 -0
- aiohomematic/model/hub/__init__.py +100 -0
- aiohomematic/model/hub/binary_sensor.py +24 -0
- aiohomematic/model/hub/button.py +28 -0
- aiohomematic/model/hub/connectivity.py +190 -0
- aiohomematic/model/hub/data_point.py +342 -0
- aiohomematic/model/hub/hub.py +864 -0
- aiohomematic/model/hub/inbox.py +135 -0
- aiohomematic/model/hub/install_mode.py +393 -0
- aiohomematic/model/hub/metrics.py +208 -0
- aiohomematic/model/hub/number.py +42 -0
- aiohomematic/model/hub/select.py +52 -0
- aiohomematic/model/hub/sensor.py +37 -0
- aiohomematic/model/hub/switch.py +43 -0
- aiohomematic/model/hub/text.py +30 -0
- aiohomematic/model/hub/update.py +221 -0
- aiohomematic/model/support.py +592 -0
- aiohomematic/model/update.py +140 -0
- aiohomematic/model/week_profile.py +1827 -0
- aiohomematic/property_decorators.py +719 -0
- aiohomematic/py.typed +0 -0
- aiohomematic/rega_scripts/accept_device_in_inbox.fn +51 -0
- aiohomematic/rega_scripts/create_backup_start.fn +28 -0
- aiohomematic/rega_scripts/create_backup_status.fn +89 -0
- aiohomematic/rega_scripts/fetch_all_device_data.fn +97 -0
- aiohomematic/rega_scripts/get_backend_info.fn +25 -0
- aiohomematic/rega_scripts/get_inbox_devices.fn +61 -0
- aiohomematic/rega_scripts/get_program_descriptions.fn +31 -0
- aiohomematic/rega_scripts/get_serial.fn +44 -0
- aiohomematic/rega_scripts/get_service_messages.fn +83 -0
- aiohomematic/rega_scripts/get_system_update_info.fn +39 -0
- aiohomematic/rega_scripts/get_system_variable_descriptions.fn +31 -0
- aiohomematic/rega_scripts/set_program_state.fn +17 -0
- aiohomematic/rega_scripts/set_system_variable.fn +19 -0
- aiohomematic/rega_scripts/trigger_firmware_update.fn +67 -0
- aiohomematic/schemas.py +256 -0
- aiohomematic/store/__init__.py +55 -0
- aiohomematic/store/dynamic/__init__.py +43 -0
- aiohomematic/store/dynamic/command.py +250 -0
- aiohomematic/store/dynamic/data.py +175 -0
- aiohomematic/store/dynamic/details.py +187 -0
- aiohomematic/store/dynamic/ping_pong.py +416 -0
- aiohomematic/store/persistent/__init__.py +71 -0
- aiohomematic/store/persistent/base.py +285 -0
- aiohomematic/store/persistent/device.py +233 -0
- aiohomematic/store/persistent/incident.py +380 -0
- aiohomematic/store/persistent/paramset.py +241 -0
- aiohomematic/store/persistent/session.py +556 -0
- aiohomematic/store/serialization.py +150 -0
- aiohomematic/store/storage.py +689 -0
- aiohomematic/store/types.py +526 -0
- aiohomematic/store/visibility/__init__.py +40 -0
- aiohomematic/store/visibility/parser.py +141 -0
- aiohomematic/store/visibility/registry.py +722 -0
- aiohomematic/store/visibility/rules.py +307 -0
- aiohomematic/strings.json +237 -0
- aiohomematic/support.py +706 -0
- aiohomematic/tracing.py +236 -0
- aiohomematic/translations/de.json +237 -0
- aiohomematic/translations/en.json +237 -0
- aiohomematic/type_aliases.py +51 -0
- aiohomematic/validator.py +128 -0
- aiohomematic-2026.1.29.dist-info/METADATA +296 -0
- aiohomematic-2026.1.29.dist-info/RECORD +188 -0
- aiohomematic-2026.1.29.dist-info/WHEEL +5 -0
- aiohomematic-2026.1.29.dist-info/entry_points.txt +2 -0
- aiohomematic-2026.1.29.dist-info/licenses/LICENSE +21 -0
- aiohomematic-2026.1.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""Decorators for central used within aiohomematic."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from functools import partial, wraps
|
|
10
|
+
import inspect
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, Final, cast
|
|
13
|
+
|
|
14
|
+
from aiohomematic import client as hmcl, i18n
|
|
15
|
+
from aiohomematic.central import rpc_server as rpc
|
|
16
|
+
from aiohomematic.const import SystemEventType
|
|
17
|
+
from aiohomematic.exceptions import AioHomematicException
|
|
18
|
+
from aiohomematic.support import extract_exc_args
|
|
19
|
+
from aiohomematic.type_aliases import CallableAny, CallableNone
|
|
20
|
+
|
|
21
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
22
|
+
_INTERFACE_ID: Final = "interface_id"
|
|
23
|
+
_CHANNEL_ADDRESS: Final = "channel_address"
|
|
24
|
+
_PARAMETER: Final = "parameter"
|
|
25
|
+
_VALUE: Final = "value"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def callback_backend_system(system_event: SystemEventType) -> Callable[[CallableAny], CallableAny]: # kwonly: disable
|
|
29
|
+
"""Check if backend_system_callback is set and call it AFTER original function."""
|
|
30
|
+
|
|
31
|
+
def decorator_backend_system_callback[**P, R](
|
|
32
|
+
func: Callable[P, R | Awaitable[R]],
|
|
33
|
+
) -> Callable[P, R | Awaitable[R]]:
|
|
34
|
+
"""Decorate callback system events."""
|
|
35
|
+
|
|
36
|
+
@wraps(func)
|
|
37
|
+
async def async_wrapper_backend_system_callback(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
38
|
+
"""Wrap async callback system events."""
|
|
39
|
+
return_value = cast(R, await func(*args, **kwargs)) # type: ignore[misc]
|
|
40
|
+
await _exec_backend_system_callback(*args, **kwargs)
|
|
41
|
+
return return_value
|
|
42
|
+
|
|
43
|
+
@wraps(func)
|
|
44
|
+
def wrapper_backend_system_callback(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
45
|
+
"""Wrap callback system events."""
|
|
46
|
+
return_value = cast(R, func(*args, **kwargs))
|
|
47
|
+
try:
|
|
48
|
+
unit = args[0]
|
|
49
|
+
looper: Any = None
|
|
50
|
+
# Check for CentralUnit (by duck typing - has looper attribute)
|
|
51
|
+
if hasattr(unit, "looper"):
|
|
52
|
+
looper = unit.looper
|
|
53
|
+
# Check for coordinator with _task_scheduler
|
|
54
|
+
elif hasattr(unit, "_task_scheduler"):
|
|
55
|
+
looper = unit._task_scheduler # pylint: disable=protected-access
|
|
56
|
+
# Check for RPCFunctions
|
|
57
|
+
elif isinstance(unit, rpc.RPCFunctions) and (
|
|
58
|
+
entry := unit.get_central_entry(interface_id=str(args[1]))
|
|
59
|
+
):
|
|
60
|
+
looper = entry.looper
|
|
61
|
+
if looper:
|
|
62
|
+
# Use partial to avoid lambda closure capturing args/kwargs
|
|
63
|
+
looper.create_task(
|
|
64
|
+
target=partial(_exec_backend_system_callback, *args, **kwargs),
|
|
65
|
+
name="wrapper_backend_system_callback",
|
|
66
|
+
)
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
_LOGGER.warning(
|
|
69
|
+
i18n.tr(
|
|
70
|
+
key="exception.central.decorators.backend_system_handler.identify_central_failed",
|
|
71
|
+
reason=extract_exc_args(exc=exc),
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
return return_value
|
|
75
|
+
|
|
76
|
+
async def _exec_backend_system_callback(*args: Any, **kwargs: Any) -> None:
|
|
77
|
+
"""Execute the callback for a system event."""
|
|
78
|
+
if not ((len(args) > 1 and not kwargs) or (len(args) == 1 and kwargs)):
|
|
79
|
+
_LOGGER.error( # i18n-log: ignore
|
|
80
|
+
"EXEC_BACKEND_SYSTEM_CALLBACK failed: *args not supported for callback_system_event"
|
|
81
|
+
)
|
|
82
|
+
try:
|
|
83
|
+
args = args[1:]
|
|
84
|
+
interface_id: str = args[0] if len(args) > 0 else str(kwargs[_INTERFACE_ID])
|
|
85
|
+
if client := hmcl.get_client(interface_id=interface_id):
|
|
86
|
+
client.modified_at = datetime.now()
|
|
87
|
+
client.central.event_coordinator.publish_system_event(system_event=system_event, **kwargs)
|
|
88
|
+
except Exception as exc: # pragma: no cover
|
|
89
|
+
_LOGGER.error( # i18n-log: ignore
|
|
90
|
+
"EXEC_BACKEND_SYSTEM_CALLBACK failed: Unable to reduce kwargs for backend_system_callback"
|
|
91
|
+
)
|
|
92
|
+
raise AioHomematicException(
|
|
93
|
+
i18n.tr(
|
|
94
|
+
key="exception.central.decorators.backend_system_handler.args_exception",
|
|
95
|
+
reason=extract_exc_args(exc=exc),
|
|
96
|
+
)
|
|
97
|
+
) from exc
|
|
98
|
+
|
|
99
|
+
if inspect.iscoroutinefunction(func):
|
|
100
|
+
return async_wrapper_backend_system_callback
|
|
101
|
+
return wrapper_backend_system_callback
|
|
102
|
+
|
|
103
|
+
return decorator_backend_system_callback
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def callback_event[**P, R](func: Callable[P, R]) -> Callable[P, R | Awaitable[R]]: # kwonly: disable
|
|
107
|
+
"""Check if event_callback is set and call it AFTER original function."""
|
|
108
|
+
|
|
109
|
+
def _exec_event_callback(*args: Any, **kwargs: Any) -> None:
|
|
110
|
+
"""Execute the callback for a data_point event."""
|
|
111
|
+
try:
|
|
112
|
+
# Expected signature: (self, interface_id, channel_address, parameter, value)
|
|
113
|
+
interface_id: str
|
|
114
|
+
if len(args) > 1:
|
|
115
|
+
interface_id = cast(str, args[1])
|
|
116
|
+
channel_address = cast(str, args[2])
|
|
117
|
+
parameter = cast(str, args[3])
|
|
118
|
+
value = args[4] if len(args) > 4 else kwargs.get(_VALUE)
|
|
119
|
+
else:
|
|
120
|
+
interface_id = cast(str, kwargs[_INTERFACE_ID])
|
|
121
|
+
channel_address = cast(str, kwargs[_CHANNEL_ADDRESS])
|
|
122
|
+
parameter = cast(str, kwargs[_PARAMETER])
|
|
123
|
+
value = kwargs[_VALUE]
|
|
124
|
+
|
|
125
|
+
if client := hmcl.get_client(interface_id=interface_id):
|
|
126
|
+
client.modified_at = datetime.now()
|
|
127
|
+
client.central.event_coordinator.publish_backend_parameter_event(
|
|
128
|
+
interface_id=interface_id, channel_address=channel_address, parameter=parameter, value=value
|
|
129
|
+
)
|
|
130
|
+
except Exception as exc: # pragma: no cover
|
|
131
|
+
_LOGGER.error( # i18n-log: ignore
|
|
132
|
+
"EXEC_DATA_POINT_EVENT_CALLBACK failed: Unable to process args/kwargs for event_callback"
|
|
133
|
+
)
|
|
134
|
+
raise AioHomematicException(
|
|
135
|
+
i18n.tr(
|
|
136
|
+
key="exception.central.decorators.event_handler.args_exception",
|
|
137
|
+
reason=extract_exc_args(exc=exc),
|
|
138
|
+
)
|
|
139
|
+
) from exc
|
|
140
|
+
|
|
141
|
+
def _schedule_or_exec(*args: Any, **kwargs: Any) -> None:
|
|
142
|
+
"""Schedule event callback on looper when possible, else execute inline."""
|
|
143
|
+
try:
|
|
144
|
+
unit = args[0]
|
|
145
|
+
looper: Any = None
|
|
146
|
+
# Check for CentralUnit (by duck typing - has looper attribute)
|
|
147
|
+
if hasattr(unit, "looper"):
|
|
148
|
+
looper = unit.looper
|
|
149
|
+
# Check for coordinator with _task_scheduler
|
|
150
|
+
elif hasattr(unit, "_task_scheduler"):
|
|
151
|
+
looper = unit._task_scheduler # pylint: disable=protected-access
|
|
152
|
+
if looper:
|
|
153
|
+
# Use partial to avoid lambda closure capturing args/kwargs
|
|
154
|
+
looper.create_task(
|
|
155
|
+
target=partial(_async_wrap_sync, _exec_event_callback, *args, **kwargs),
|
|
156
|
+
name="wrapper_event_callback",
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
except Exception:
|
|
160
|
+
# Fall through to inline execution on any error
|
|
161
|
+
pass
|
|
162
|
+
_exec_event_callback(*args, **kwargs)
|
|
163
|
+
|
|
164
|
+
@wraps(func)
|
|
165
|
+
async def async_wrapper_event_callback(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
166
|
+
"""Wrap async callback events."""
|
|
167
|
+
return_value = cast(R, await func(*args, **kwargs)) # type: ignore[misc]
|
|
168
|
+
_schedule_or_exec(*args, **kwargs)
|
|
169
|
+
return return_value
|
|
170
|
+
|
|
171
|
+
@wraps(func)
|
|
172
|
+
def wrapper_event_callback(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
173
|
+
"""Wrap sync callback events."""
|
|
174
|
+
return_value = func(*args, **kwargs)
|
|
175
|
+
_schedule_or_exec(*args, **kwargs)
|
|
176
|
+
return return_value
|
|
177
|
+
|
|
178
|
+
# Helper to create a trivial coroutine from a sync callable
|
|
179
|
+
async def _async_wrap_sync(cb: CallableNone, *a: Any, **kw: Any) -> None:
|
|
180
|
+
cb(*a, **kw)
|
|
181
|
+
|
|
182
|
+
if inspect.iscoroutinefunction(func):
|
|
183
|
+
return async_wrapper_event_callback
|
|
184
|
+
return wrapper_event_callback
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Device registry for managing device and channel collections.
|
|
5
|
+
|
|
6
|
+
This module provides a centralized registry for device management within the central unit.
|
|
7
|
+
It separates device storage concerns from device lifecycle management.
|
|
8
|
+
|
|
9
|
+
The DeviceRegistry provides:
|
|
10
|
+
- Device storage and lookup by address
|
|
11
|
+
- Channel lookup by channel address
|
|
12
|
+
- Device iteration and filtering
|
|
13
|
+
- Channel identification within text
|
|
14
|
+
- Virtual remote device access
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import logging
|
|
21
|
+
from typing import Final
|
|
22
|
+
|
|
23
|
+
from aiohomematic.interfaces import CentralInfoProtocol, ChannelProtocol, ClientProviderProtocol, DeviceProtocol
|
|
24
|
+
from aiohomematic.support import get_device_address
|
|
25
|
+
|
|
26
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DeviceRegistry:
|
|
30
|
+
"""Registry for device and channel management."""
|
|
31
|
+
|
|
32
|
+
__slots__ = (
|
|
33
|
+
"_central_info",
|
|
34
|
+
"_client_provider",
|
|
35
|
+
"_devices",
|
|
36
|
+
"_lock",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
central_info: CentralInfoProtocol,
|
|
43
|
+
client_provider: ClientProviderProtocol,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Initialize the device registry.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
----
|
|
50
|
+
central_info: Provider for central system information
|
|
51
|
+
client_provider: Provider for client access
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
self._central_info: Final = central_info
|
|
55
|
+
self._client_provider: Final = client_provider
|
|
56
|
+
# {device_address, device}
|
|
57
|
+
self._devices: Final[dict[str, DeviceProtocol]] = {}
|
|
58
|
+
self._lock: Final = asyncio.Lock()
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def device_count(self) -> int:
|
|
62
|
+
"""
|
|
63
|
+
Return the count of devices in the registry.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
Number of devices
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
return len(self._devices)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def devices(self) -> tuple[DeviceProtocol, ...]:
|
|
74
|
+
"""
|
|
75
|
+
Return all devices as a tuple.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
Tuple of all Device instances
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
return tuple(self._devices.values())
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def models(self) -> tuple[str, ...]:
|
|
86
|
+
"""
|
|
87
|
+
Return the models of the devices in the registry.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
Models of all devices
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
return tuple(sorted({d.model for d in self.devices}))
|
|
95
|
+
|
|
96
|
+
async def add_device(self, *, device: DeviceProtocol) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Add a device to the registry.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
----
|
|
102
|
+
device: Device instance to add
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
async with self._lock:
|
|
106
|
+
self._devices[device.address] = device
|
|
107
|
+
_LOGGER.debug(
|
|
108
|
+
"ADD_DEVICE: Added device %s to registry for %s",
|
|
109
|
+
device.address,
|
|
110
|
+
self._central_info.name,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
async def clear(self) -> None:
|
|
114
|
+
"""Clear all devices from the registry."""
|
|
115
|
+
async with self._lock:
|
|
116
|
+
self._devices.clear()
|
|
117
|
+
_LOGGER.debug("CLEAR: Cleared device registry for %s", self._central_info.name)
|
|
118
|
+
|
|
119
|
+
def get_channel(self, *, channel_address: str) -> ChannelProtocol | None:
|
|
120
|
+
"""
|
|
121
|
+
Get a channel by channel address.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
----
|
|
125
|
+
channel_address: Channel address (e.g., "VCU0000001:1")
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
-------
|
|
129
|
+
Channel instance or None if not found
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
if device := self.get_device(address=channel_address):
|
|
133
|
+
return device.get_channel(channel_address=channel_address)
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def get_device(self, *, address: str) -> DeviceProtocol | None:
|
|
137
|
+
"""
|
|
138
|
+
Get a device by address.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
----
|
|
142
|
+
address: Device address or channel address
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
-------
|
|
146
|
+
Device instance or None if not found
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
d_address = get_device_address(address=address)
|
|
150
|
+
return self._devices.get(d_address)
|
|
151
|
+
|
|
152
|
+
def get_device_addresses(self) -> frozenset[str]:
|
|
153
|
+
"""
|
|
154
|
+
Get all device addresses in the registry.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
Frozen set of device addresses
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
return frozenset(self._devices.keys())
|
|
162
|
+
|
|
163
|
+
def get_virtual_remotes(self) -> tuple[DeviceProtocol, ...]:
|
|
164
|
+
"""
|
|
165
|
+
Get all virtual remote devices from clients.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
Tuple of virtual remote Device instances
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
return tuple(vr for cl in self._client_provider.clients if (vr := cl.get_virtual_remote()) is not None)
|
|
173
|
+
|
|
174
|
+
def has_device(self, *, address: str) -> bool:
|
|
175
|
+
"""
|
|
176
|
+
Check if a device exists in the registry.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
----
|
|
180
|
+
address: Device address
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
-------
|
|
184
|
+
True if device exists, False otherwise
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
return address in self._devices
|
|
188
|
+
|
|
189
|
+
def identify_channel(self, *, text: str) -> ChannelProtocol | None:
|
|
190
|
+
"""
|
|
191
|
+
Identify a channel within a text string.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
----
|
|
195
|
+
text: Text to search for channel identification
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
-------
|
|
199
|
+
Channel instance or None if not found
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
for device in self._devices.values():
|
|
203
|
+
if channel := device.identify_channel(text=text):
|
|
204
|
+
return channel
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
async def remove_device(self, *, device_address: str) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Remove a device from the registry.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
----
|
|
213
|
+
device_address: Address of the device to remove
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
async with self._lock:
|
|
217
|
+
if device_address not in self._devices:
|
|
218
|
+
_LOGGER.debug(
|
|
219
|
+
"REMOVE_DEVICE: Device %s not found in registry for %s",
|
|
220
|
+
device_address,
|
|
221
|
+
self._central_info.name,
|
|
222
|
+
)
|
|
223
|
+
return
|
|
224
|
+
del self._devices[device_address]
|
|
225
|
+
_LOGGER.debug(
|
|
226
|
+
"REMOVE_DEVICE: Removed device %s from registry for %s",
|
|
227
|
+
device_address,
|
|
228
|
+
self._central_info.name,
|
|
229
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Events sub-package for the central event system.
|
|
5
|
+
|
|
6
|
+
This package contains the event bus infrastructure and event type definitions:
|
|
7
|
+
|
|
8
|
+
- EventBus: Core event bus for type-safe event subscription and publishing
|
|
9
|
+
- Event types: DataPointValueReceivedEvent, DeviceStateChangedEvent, etc.
|
|
10
|
+
- Integration events: SystemStatusChangedEvent for Home Assistant integration
|
|
11
|
+
|
|
12
|
+
Public API of this module is defined by __all__.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from aiohomematic.central.events.bus import (
|
|
18
|
+
CacheInvalidatedEvent,
|
|
19
|
+
ConnectionHealthChangedEvent,
|
|
20
|
+
ConnectionLostEvent,
|
|
21
|
+
ConnectionStageChangedEvent,
|
|
22
|
+
DataPointStateChangedEvent,
|
|
23
|
+
DataPointStatusReceivedEvent,
|
|
24
|
+
DataPointValueReceivedEvent,
|
|
25
|
+
DataRefreshCompletedEvent,
|
|
26
|
+
DataRefreshTriggeredEvent,
|
|
27
|
+
DeviceRemovedEvent,
|
|
28
|
+
DeviceStateChangedEvent,
|
|
29
|
+
EventBatch,
|
|
30
|
+
EventBus,
|
|
31
|
+
FirmwareStateChangedEvent,
|
|
32
|
+
HandlerStats,
|
|
33
|
+
HeartbeatTimerFiredEvent,
|
|
34
|
+
LinkPeerChangedEvent,
|
|
35
|
+
ProgramExecutedEvent,
|
|
36
|
+
RecoveryAttemptedEvent,
|
|
37
|
+
RecoveryCompletedEvent,
|
|
38
|
+
RecoveryFailedEvent,
|
|
39
|
+
RecoveryStageChangedEvent,
|
|
40
|
+
RequestCoalescedEvent,
|
|
41
|
+
RpcParameterReceivedEvent,
|
|
42
|
+
SysvarStateChangedEvent,
|
|
43
|
+
)
|
|
44
|
+
from aiohomematic.central.events.integration import (
|
|
45
|
+
DataPointsCreatedEvent,
|
|
46
|
+
DeviceLifecycleEvent,
|
|
47
|
+
DeviceLifecycleEventType,
|
|
48
|
+
DeviceTriggerEvent,
|
|
49
|
+
IntegrationIssue,
|
|
50
|
+
SystemStatusChangedEvent,
|
|
51
|
+
)
|
|
52
|
+
from aiohomematic.central.events.types import (
|
|
53
|
+
CentralStateChangedEvent,
|
|
54
|
+
CircuitBreakerStateChangedEvent,
|
|
55
|
+
CircuitBreakerTrippedEvent,
|
|
56
|
+
ClientStateChangedEvent,
|
|
57
|
+
Event,
|
|
58
|
+
EventPriority,
|
|
59
|
+
HealthRecordedEvent,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
# Event types
|
|
64
|
+
"CacheInvalidatedEvent",
|
|
65
|
+
"CentralStateChangedEvent",
|
|
66
|
+
"CircuitBreakerStateChangedEvent",
|
|
67
|
+
"CircuitBreakerTrippedEvent",
|
|
68
|
+
"ClientStateChangedEvent",
|
|
69
|
+
"ConnectionHealthChangedEvent",
|
|
70
|
+
"ConnectionLostEvent",
|
|
71
|
+
"ConnectionStageChangedEvent",
|
|
72
|
+
"DataPointStateChangedEvent",
|
|
73
|
+
"DataPointStatusReceivedEvent",
|
|
74
|
+
"DataPointValueReceivedEvent",
|
|
75
|
+
"DataRefreshCompletedEvent",
|
|
76
|
+
"DataRefreshTriggeredEvent",
|
|
77
|
+
"DeviceRemovedEvent",
|
|
78
|
+
"DeviceStateChangedEvent",
|
|
79
|
+
"FirmwareStateChangedEvent",
|
|
80
|
+
"HealthRecordedEvent",
|
|
81
|
+
"HeartbeatTimerFiredEvent",
|
|
82
|
+
"LinkPeerChangedEvent",
|
|
83
|
+
"ProgramExecutedEvent",
|
|
84
|
+
"RecoveryAttemptedEvent",
|
|
85
|
+
"RecoveryCompletedEvent",
|
|
86
|
+
"RecoveryFailedEvent",
|
|
87
|
+
"RecoveryStageChangedEvent",
|
|
88
|
+
"RequestCoalescedEvent",
|
|
89
|
+
"RpcParameterReceivedEvent",
|
|
90
|
+
"SysvarStateChangedEvent",
|
|
91
|
+
# EventBus core
|
|
92
|
+
"Event",
|
|
93
|
+
"EventBatch",
|
|
94
|
+
"EventBus",
|
|
95
|
+
"EventPriority",
|
|
96
|
+
"HandlerStats",
|
|
97
|
+
# Integration events
|
|
98
|
+
"DataPointsCreatedEvent",
|
|
99
|
+
"DeviceLifecycleEvent",
|
|
100
|
+
"DeviceLifecycleEventType",
|
|
101
|
+
"DeviceTriggerEvent",
|
|
102
|
+
"IntegrationIssue",
|
|
103
|
+
"SystemStatusChangedEvent",
|
|
104
|
+
]
|