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.
Files changed (188) hide show
  1. aiohomematic/__init__.py +110 -0
  2. aiohomematic/_log_context_protocol.py +29 -0
  3. aiohomematic/api.py +410 -0
  4. aiohomematic/async_support.py +250 -0
  5. aiohomematic/backend_detection.py +462 -0
  6. aiohomematic/central/__init__.py +103 -0
  7. aiohomematic/central/async_rpc_server.py +760 -0
  8. aiohomematic/central/central_unit.py +1152 -0
  9. aiohomematic/central/config.py +463 -0
  10. aiohomematic/central/config_builder.py +772 -0
  11. aiohomematic/central/connection_state.py +160 -0
  12. aiohomematic/central/coordinators/__init__.py +38 -0
  13. aiohomematic/central/coordinators/cache.py +414 -0
  14. aiohomematic/central/coordinators/client.py +480 -0
  15. aiohomematic/central/coordinators/connection_recovery.py +1141 -0
  16. aiohomematic/central/coordinators/device.py +1166 -0
  17. aiohomematic/central/coordinators/event.py +514 -0
  18. aiohomematic/central/coordinators/hub.py +532 -0
  19. aiohomematic/central/decorators.py +184 -0
  20. aiohomematic/central/device_registry.py +229 -0
  21. aiohomematic/central/events/__init__.py +104 -0
  22. aiohomematic/central/events/bus.py +1392 -0
  23. aiohomematic/central/events/integration.py +424 -0
  24. aiohomematic/central/events/types.py +194 -0
  25. aiohomematic/central/health.py +762 -0
  26. aiohomematic/central/rpc_server.py +353 -0
  27. aiohomematic/central/scheduler.py +794 -0
  28. aiohomematic/central/state_machine.py +391 -0
  29. aiohomematic/client/__init__.py +203 -0
  30. aiohomematic/client/_rpc_errors.py +187 -0
  31. aiohomematic/client/backends/__init__.py +48 -0
  32. aiohomematic/client/backends/base.py +335 -0
  33. aiohomematic/client/backends/capabilities.py +138 -0
  34. aiohomematic/client/backends/ccu.py +487 -0
  35. aiohomematic/client/backends/factory.py +116 -0
  36. aiohomematic/client/backends/homegear.py +294 -0
  37. aiohomematic/client/backends/json_ccu.py +252 -0
  38. aiohomematic/client/backends/protocol.py +316 -0
  39. aiohomematic/client/ccu.py +1857 -0
  40. aiohomematic/client/circuit_breaker.py +459 -0
  41. aiohomematic/client/config.py +64 -0
  42. aiohomematic/client/handlers/__init__.py +40 -0
  43. aiohomematic/client/handlers/backup.py +157 -0
  44. aiohomematic/client/handlers/base.py +79 -0
  45. aiohomematic/client/handlers/device_ops.py +1085 -0
  46. aiohomematic/client/handlers/firmware.py +144 -0
  47. aiohomematic/client/handlers/link_mgmt.py +199 -0
  48. aiohomematic/client/handlers/metadata.py +436 -0
  49. aiohomematic/client/handlers/programs.py +144 -0
  50. aiohomematic/client/handlers/sysvars.py +100 -0
  51. aiohomematic/client/interface_client.py +1304 -0
  52. aiohomematic/client/json_rpc.py +2068 -0
  53. aiohomematic/client/request_coalescer.py +282 -0
  54. aiohomematic/client/rpc_proxy.py +629 -0
  55. aiohomematic/client/state_machine.py +324 -0
  56. aiohomematic/const.py +2207 -0
  57. aiohomematic/context.py +275 -0
  58. aiohomematic/converter.py +270 -0
  59. aiohomematic/decorators.py +390 -0
  60. aiohomematic/exceptions.py +185 -0
  61. aiohomematic/hmcli.py +997 -0
  62. aiohomematic/i18n.py +193 -0
  63. aiohomematic/interfaces/__init__.py +407 -0
  64. aiohomematic/interfaces/central.py +1067 -0
  65. aiohomematic/interfaces/client.py +1096 -0
  66. aiohomematic/interfaces/coordinators.py +63 -0
  67. aiohomematic/interfaces/model.py +1921 -0
  68. aiohomematic/interfaces/operations.py +217 -0
  69. aiohomematic/logging_context.py +134 -0
  70. aiohomematic/metrics/__init__.py +125 -0
  71. aiohomematic/metrics/_protocols.py +140 -0
  72. aiohomematic/metrics/aggregator.py +534 -0
  73. aiohomematic/metrics/dataclasses.py +489 -0
  74. aiohomematic/metrics/emitter.py +292 -0
  75. aiohomematic/metrics/events.py +183 -0
  76. aiohomematic/metrics/keys.py +300 -0
  77. aiohomematic/metrics/observer.py +563 -0
  78. aiohomematic/metrics/stats.py +172 -0
  79. aiohomematic/model/__init__.py +189 -0
  80. aiohomematic/model/availability.py +65 -0
  81. aiohomematic/model/calculated/__init__.py +89 -0
  82. aiohomematic/model/calculated/climate.py +276 -0
  83. aiohomematic/model/calculated/data_point.py +315 -0
  84. aiohomematic/model/calculated/field.py +147 -0
  85. aiohomematic/model/calculated/operating_voltage_level.py +286 -0
  86. aiohomematic/model/calculated/support.py +232 -0
  87. aiohomematic/model/custom/__init__.py +214 -0
  88. aiohomematic/model/custom/capabilities/__init__.py +67 -0
  89. aiohomematic/model/custom/capabilities/climate.py +41 -0
  90. aiohomematic/model/custom/capabilities/light.py +87 -0
  91. aiohomematic/model/custom/capabilities/lock.py +44 -0
  92. aiohomematic/model/custom/capabilities/siren.py +63 -0
  93. aiohomematic/model/custom/climate.py +1130 -0
  94. aiohomematic/model/custom/cover.py +722 -0
  95. aiohomematic/model/custom/data_point.py +360 -0
  96. aiohomematic/model/custom/definition.py +300 -0
  97. aiohomematic/model/custom/field.py +89 -0
  98. aiohomematic/model/custom/light.py +1174 -0
  99. aiohomematic/model/custom/lock.py +322 -0
  100. aiohomematic/model/custom/mixins.py +445 -0
  101. aiohomematic/model/custom/profile.py +945 -0
  102. aiohomematic/model/custom/registry.py +251 -0
  103. aiohomematic/model/custom/siren.py +462 -0
  104. aiohomematic/model/custom/switch.py +195 -0
  105. aiohomematic/model/custom/text_display.py +289 -0
  106. aiohomematic/model/custom/valve.py +78 -0
  107. aiohomematic/model/data_point.py +1416 -0
  108. aiohomematic/model/device.py +1840 -0
  109. aiohomematic/model/event.py +216 -0
  110. aiohomematic/model/generic/__init__.py +327 -0
  111. aiohomematic/model/generic/action.py +40 -0
  112. aiohomematic/model/generic/action_select.py +62 -0
  113. aiohomematic/model/generic/binary_sensor.py +30 -0
  114. aiohomematic/model/generic/button.py +31 -0
  115. aiohomematic/model/generic/data_point.py +177 -0
  116. aiohomematic/model/generic/dummy.py +150 -0
  117. aiohomematic/model/generic/number.py +76 -0
  118. aiohomematic/model/generic/select.py +56 -0
  119. aiohomematic/model/generic/sensor.py +76 -0
  120. aiohomematic/model/generic/switch.py +54 -0
  121. aiohomematic/model/generic/text.py +33 -0
  122. aiohomematic/model/hub/__init__.py +100 -0
  123. aiohomematic/model/hub/binary_sensor.py +24 -0
  124. aiohomematic/model/hub/button.py +28 -0
  125. aiohomematic/model/hub/connectivity.py +190 -0
  126. aiohomematic/model/hub/data_point.py +342 -0
  127. aiohomematic/model/hub/hub.py +864 -0
  128. aiohomematic/model/hub/inbox.py +135 -0
  129. aiohomematic/model/hub/install_mode.py +393 -0
  130. aiohomematic/model/hub/metrics.py +208 -0
  131. aiohomematic/model/hub/number.py +42 -0
  132. aiohomematic/model/hub/select.py +52 -0
  133. aiohomematic/model/hub/sensor.py +37 -0
  134. aiohomematic/model/hub/switch.py +43 -0
  135. aiohomematic/model/hub/text.py +30 -0
  136. aiohomematic/model/hub/update.py +221 -0
  137. aiohomematic/model/support.py +592 -0
  138. aiohomematic/model/update.py +140 -0
  139. aiohomematic/model/week_profile.py +1827 -0
  140. aiohomematic/property_decorators.py +719 -0
  141. aiohomematic/py.typed +0 -0
  142. aiohomematic/rega_scripts/accept_device_in_inbox.fn +51 -0
  143. aiohomematic/rega_scripts/create_backup_start.fn +28 -0
  144. aiohomematic/rega_scripts/create_backup_status.fn +89 -0
  145. aiohomematic/rega_scripts/fetch_all_device_data.fn +97 -0
  146. aiohomematic/rega_scripts/get_backend_info.fn +25 -0
  147. aiohomematic/rega_scripts/get_inbox_devices.fn +61 -0
  148. aiohomematic/rega_scripts/get_program_descriptions.fn +31 -0
  149. aiohomematic/rega_scripts/get_serial.fn +44 -0
  150. aiohomematic/rega_scripts/get_service_messages.fn +83 -0
  151. aiohomematic/rega_scripts/get_system_update_info.fn +39 -0
  152. aiohomematic/rega_scripts/get_system_variable_descriptions.fn +31 -0
  153. aiohomematic/rega_scripts/set_program_state.fn +17 -0
  154. aiohomematic/rega_scripts/set_system_variable.fn +19 -0
  155. aiohomematic/rega_scripts/trigger_firmware_update.fn +67 -0
  156. aiohomematic/schemas.py +256 -0
  157. aiohomematic/store/__init__.py +55 -0
  158. aiohomematic/store/dynamic/__init__.py +43 -0
  159. aiohomematic/store/dynamic/command.py +250 -0
  160. aiohomematic/store/dynamic/data.py +175 -0
  161. aiohomematic/store/dynamic/details.py +187 -0
  162. aiohomematic/store/dynamic/ping_pong.py +416 -0
  163. aiohomematic/store/persistent/__init__.py +71 -0
  164. aiohomematic/store/persistent/base.py +285 -0
  165. aiohomematic/store/persistent/device.py +233 -0
  166. aiohomematic/store/persistent/incident.py +380 -0
  167. aiohomematic/store/persistent/paramset.py +241 -0
  168. aiohomematic/store/persistent/session.py +556 -0
  169. aiohomematic/store/serialization.py +150 -0
  170. aiohomematic/store/storage.py +689 -0
  171. aiohomematic/store/types.py +526 -0
  172. aiohomematic/store/visibility/__init__.py +40 -0
  173. aiohomematic/store/visibility/parser.py +141 -0
  174. aiohomematic/store/visibility/registry.py +722 -0
  175. aiohomematic/store/visibility/rules.py +307 -0
  176. aiohomematic/strings.json +237 -0
  177. aiohomematic/support.py +706 -0
  178. aiohomematic/tracing.py +236 -0
  179. aiohomematic/translations/de.json +237 -0
  180. aiohomematic/translations/en.json +237 -0
  181. aiohomematic/type_aliases.py +51 -0
  182. aiohomematic/validator.py +128 -0
  183. aiohomematic-2026.1.29.dist-info/METADATA +296 -0
  184. aiohomematic-2026.1.29.dist-info/RECORD +188 -0
  185. aiohomematic-2026.1.29.dist-info/WHEEL +5 -0
  186. aiohomematic-2026.1.29.dist-info/entry_points.txt +2 -0
  187. aiohomematic-2026.1.29.dist-info/licenses/LICENSE +21 -0
  188. 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
+ ]