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,532 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Hub coordinator for managing programs and system variables.
5
+
6
+ This module provides centralized management of system-level data points like
7
+ programs and system variables that are exposed through the Hub.
8
+
9
+ The HubCoordinator provides:
10
+ - Program data point management
11
+ - System variable data point management
12
+ - Hub data refresh coordination
13
+ - Program execution and state management
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections.abc import Mapping
19
+ from datetime import datetime
20
+ import logging
21
+ from typing import Any, Final
22
+
23
+ from aiohomematic import i18n
24
+ from aiohomematic.central.events import ProgramExecutedEvent, SysvarStateChangedEvent
25
+ from aiohomematic.const import DataPointCategory, Interface, ProgramTrigger
26
+ from aiohomematic.decorators import inspector
27
+ from aiohomematic.interfaces import (
28
+ CentralInfoProtocol,
29
+ ChannelLookupProtocol,
30
+ ClientProviderProtocol,
31
+ ConfigProviderProtocol,
32
+ EventBusProviderProtocol,
33
+ EventPublisherProtocol,
34
+ GenericProgramDataPointProtocol,
35
+ GenericSysvarDataPointProtocol,
36
+ HealthTrackerProtocol,
37
+ HubDataFetcherProtocol,
38
+ HubDataPointManagerProtocol,
39
+ MetricsProviderProtocol,
40
+ ParameterVisibilityProviderProtocol,
41
+ ParamsetDescriptionProviderProtocol,
42
+ PrimaryClientProviderProtocol,
43
+ TaskSchedulerProtocol,
44
+ )
45
+ from aiohomematic.model.hub import ConnectivityDpType, Hub, InstallModeDpType, MetricsDpType, ProgramDpType
46
+ from aiohomematic.property_decorators import DelegatedProperty
47
+ from aiohomematic.type_aliases import UnsubscribeCallback
48
+
49
+ _LOGGER: Final = logging.getLogger(__name__)
50
+
51
+
52
+ class HubCoordinator(HubDataFetcherProtocol, HubDataPointManagerProtocol):
53
+ """Coordinator for hub-level data points (programs and system variables)."""
54
+
55
+ __slots__ = (
56
+ "_central_info",
57
+ "_event_bus_provider",
58
+ "_hub",
59
+ "_primary_client_provider",
60
+ "_program_data_points",
61
+ "_state_path_to_name",
62
+ "_sysvar_data_points",
63
+ "_sysvar_unsubscribes",
64
+ )
65
+
66
+ def __init__(
67
+ self,
68
+ *,
69
+ central_info: CentralInfoProtocol,
70
+ channel_lookup: ChannelLookupProtocol,
71
+ client_provider: ClientProviderProtocol,
72
+ config_provider: ConfigProviderProtocol,
73
+ event_bus_provider: EventBusProviderProtocol,
74
+ event_publisher: EventPublisherProtocol,
75
+ health_tracker: HealthTrackerProtocol,
76
+ metrics_provider: MetricsProviderProtocol,
77
+ parameter_visibility_provider: ParameterVisibilityProviderProtocol,
78
+ paramset_description_provider: ParamsetDescriptionProviderProtocol,
79
+ primary_client_provider: PrimaryClientProviderProtocol,
80
+ task_scheduler: TaskSchedulerProtocol,
81
+ ) -> None:
82
+ """
83
+ Initialize the hub coordinator.
84
+
85
+ Args:
86
+ ----
87
+ central_info: Provider for central system information
88
+ channel_lookup: Provider for channel lookup operations
89
+ client_provider: Provider for client access by interface
90
+ config_provider: Provider for configuration access
91
+ event_bus_provider: Provider for event bus access
92
+ event_publisher: Provider for event emission
93
+ health_tracker: Provider for connection health tracking
94
+ metrics_provider: Provider for metrics aggregator access
95
+ parameter_visibility_provider: Provider for parameter visibility rules
96
+ paramset_description_provider: Provider for paramset descriptions
97
+ primary_client_provider: Provider for primary client access
98
+ task_scheduler: Scheduler for async tasks
99
+
100
+ """
101
+ self._central_info: Final = central_info
102
+ self._event_bus_provider: Final = event_bus_provider
103
+ self._primary_client_provider: Final = primary_client_provider
104
+
105
+ # {sysvar_name, sysvar_data_point}
106
+ self._sysvar_data_points: Final[dict[str, GenericSysvarDataPointProtocol]] = {}
107
+ # {program_name, program_button}
108
+ self._program_data_points: Final[dict[str, ProgramDpType]] = {}
109
+ self._state_path_to_name: Final[dict[str, str]] = {}
110
+ # Unsubscribe callbacks for sysvar event subscriptions
111
+ self._sysvar_unsubscribes: Final[list[UnsubscribeCallback]] = []
112
+
113
+ # Create Hub with protocol interfaces
114
+ self._hub: Final = Hub(
115
+ central_info=central_info,
116
+ channel_lookup=channel_lookup,
117
+ client_provider=client_provider,
118
+ config_provider=config_provider,
119
+ event_bus_provider=event_bus_provider,
120
+ event_publisher=event_publisher,
121
+ health_tracker=health_tracker,
122
+ hub_data_fetcher=self,
123
+ hub_data_point_manager=self,
124
+ metrics_provider=metrics_provider,
125
+ parameter_visibility_provider=parameter_visibility_provider,
126
+ paramset_description_provider=paramset_description_provider,
127
+ primary_client_provider=primary_client_provider,
128
+ task_scheduler=task_scheduler,
129
+ )
130
+
131
+ connectivity_dps: Final = DelegatedProperty[Mapping[str, ConnectivityDpType]](path="_hub.connectivity_dps")
132
+ install_mode_dps: Final = DelegatedProperty[Mapping[Interface, InstallModeDpType]](path="_hub.install_mode_dps")
133
+ metrics_dps: Final = DelegatedProperty[MetricsDpType | None](path="_hub.metrics_dps")
134
+
135
+ @property
136
+ def data_point_paths(self) -> tuple[str, ...]:
137
+ """Return the data point paths."""
138
+ return tuple(self._state_path_to_name.keys())
139
+
140
+ @property
141
+ def program_data_points(self) -> tuple[GenericProgramDataPointProtocol, ...]:
142
+ """Return the program data points (both buttons and switches)."""
143
+ return tuple(
144
+ [x.button for x in self._program_data_points.values()]
145
+ + [x.switch for x in self._program_data_points.values()]
146
+ )
147
+
148
+ @property
149
+ def sysvar_data_points(self) -> tuple[GenericSysvarDataPointProtocol, ...]:
150
+ """Return the sysvar data points."""
151
+ return tuple(self._sysvar_data_points.values())
152
+
153
+ def add_program_data_point(self, *, program_dp: ProgramDpType) -> None:
154
+ """
155
+ Add new program data point.
156
+
157
+ Args:
158
+ ----
159
+ program_dp: Program data point to add
160
+
161
+ """
162
+ self._program_data_points[program_dp.pid] = program_dp
163
+ self._state_path_to_name[program_dp.button.state_path] = program_dp.pid
164
+ _LOGGER.debug(
165
+ "ADD_PROGRAM_DATA_POINT: Added program %s to %s",
166
+ program_dp.pid,
167
+ self._central_info.name,
168
+ )
169
+
170
+ def add_sysvar_data_point(self, *, sysvar_data_point: GenericSysvarDataPointProtocol) -> None:
171
+ """
172
+ Add new system variable data point.
173
+
174
+ Args:
175
+ ----
176
+ sysvar_data_point: System variable data point to add
177
+
178
+ """
179
+ if (vid := sysvar_data_point.vid) is not None:
180
+ self._sysvar_data_points[vid] = sysvar_data_point
181
+ _LOGGER.debug(
182
+ "ADD_SYSVAR_DATA_POINT: Added sysvar %s to %s",
183
+ vid,
184
+ self._central_info.name,
185
+ )
186
+
187
+ self._state_path_to_name[sysvar_data_point.state_path] = sysvar_data_point.vid
188
+
189
+ # Add event subscription for this sysvar via EventBus with filtering
190
+ async def event_handler(*, event: SysvarStateChangedEvent) -> None:
191
+ """Filter and handle sysvar events."""
192
+ if event.state_path == sysvar_data_point.state_path:
193
+ await sysvar_data_point.event(value=event.value, received_at=event.received_at)
194
+
195
+ self._sysvar_unsubscribes.append(
196
+ self._event_bus_provider.event_bus.subscribe(
197
+ event_type=SysvarStateChangedEvent, event_key=sysvar_data_point.state_path, handler=event_handler
198
+ )
199
+ )
200
+
201
+ def clear(self) -> None:
202
+ """
203
+ Clear all sysvar event subscriptions.
204
+
205
+ Call this method when stopping the central unit to prevent leaked subscriptions.
206
+ """
207
+ for unsubscribe in self._sysvar_unsubscribes:
208
+ unsubscribe()
209
+ self._sysvar_unsubscribes.clear()
210
+ _LOGGER.debug("CLEAR: Cleared %s sysvar event subscriptions", self._central_info.name)
211
+
212
+ def create_install_mode_dps(self) -> Mapping[Interface, InstallModeDpType]:
213
+ """
214
+ Create install mode data points for all supported interfaces.
215
+
216
+ Returns a dict of InstallModeDpType by Interface.
217
+ """
218
+ return self._hub.create_install_mode_dps()
219
+
220
+ async def execute_program(self, *, pid: str) -> bool:
221
+ """
222
+ Execute a program on the backend.
223
+
224
+ Args:
225
+ ----
226
+ pid: Program identifier
227
+
228
+ Returns:
229
+ -------
230
+ True if execution succeeded, False otherwise
231
+
232
+ """
233
+ if client := self._primary_client_provider.primary_client:
234
+ success = await client.execute_program(pid=pid)
235
+
236
+ # Emit program executed event
237
+ program_name = pid # Default to pid if name not found
238
+ if program_dp := self._program_data_points.get(pid):
239
+ program_name = program_dp.button.name
240
+
241
+ await self._event_bus_provider.event_bus.publish(
242
+ event=ProgramExecutedEvent(
243
+ timestamp=datetime.now(),
244
+ program_id=pid,
245
+ program_name=program_name,
246
+ triggered_by=ProgramTrigger.API,
247
+ success=success,
248
+ )
249
+ )
250
+ return success
251
+ return False
252
+
253
+ def fetch_connectivity_data(self, *, scheduled: bool) -> None:
254
+ """
255
+ Refresh connectivity binary sensors with current values.
256
+
257
+ Args:
258
+ ----
259
+ scheduled: Whether this is a scheduled refresh
260
+
261
+ """
262
+ self._hub.fetch_connectivity_data(scheduled=scheduled)
263
+
264
+ @inspector(re_raise=False)
265
+ async def fetch_inbox_data(self, *, scheduled: bool) -> None:
266
+ """
267
+ Fetch inbox data from the backend.
268
+
269
+ Args:
270
+ ----
271
+ scheduled: Whether this is a scheduled refresh
272
+
273
+ """
274
+ await self._hub.fetch_inbox_data(scheduled=scheduled)
275
+
276
+ @inspector(re_raise=False)
277
+ async def fetch_install_mode_data(self, *, scheduled: bool) -> None:
278
+ """
279
+ Fetch install mode data from the backend.
280
+
281
+ Args:
282
+ ----
283
+ scheduled: Whether this is a scheduled refresh
284
+
285
+ """
286
+ await self._hub.fetch_install_mode_data(scheduled=scheduled)
287
+
288
+ def fetch_metrics_data(self, *, scheduled: bool) -> None:
289
+ """
290
+ Refresh metrics hub sensors with current values.
291
+
292
+ Args:
293
+ ----
294
+ scheduled: Whether this is a scheduled refresh
295
+
296
+ """
297
+ self._hub.fetch_metrics_data(scheduled=scheduled)
298
+
299
+ @inspector(re_raise=False)
300
+ async def fetch_program_data(self, *, scheduled: bool) -> None:
301
+ """
302
+ Fetch program data from the backend.
303
+
304
+ Args:
305
+ ----
306
+ scheduled: Whether this is a scheduled refresh
307
+
308
+ """
309
+ await self._hub.fetch_program_data(scheduled=scheduled)
310
+
311
+ @inspector(re_raise=False)
312
+ async def fetch_system_update_data(self, *, scheduled: bool) -> None:
313
+ """
314
+ Fetch system update data from the backend.
315
+
316
+ Args:
317
+ ----
318
+ scheduled: Whether this is a scheduled refresh
319
+
320
+ """
321
+ await self._hub.fetch_system_update_data(scheduled=scheduled)
322
+
323
+ @inspector(re_raise=False)
324
+ async def fetch_sysvar_data(self, *, scheduled: bool) -> None:
325
+ """
326
+ Fetch system variable data from the backend.
327
+
328
+ Args:
329
+ ----
330
+ scheduled: Whether this is a scheduled refresh
331
+
332
+ """
333
+ await self._hub.fetch_sysvar_data(scheduled=scheduled)
334
+
335
+ def get_hub_data_points(
336
+ self, *, category: DataPointCategory | None = None, registered: bool | None = None
337
+ ) -> tuple[GenericProgramDataPointProtocol | GenericSysvarDataPointProtocol, ...]:
338
+ """
339
+ Return the hub data points (programs and sysvars) filtered by category and registration.
340
+
341
+ Args:
342
+ ----
343
+ category: Optional category to filter by
344
+ registered: Optional registration status to filter by
345
+
346
+ Returns:
347
+ -------
348
+ Tuple of matching hub data points
349
+
350
+ """
351
+ return tuple(
352
+ he
353
+ for he in (self.program_data_points + self.sysvar_data_points)
354
+ if (category is None or he.category == category) and (registered is None or he.is_registered == registered)
355
+ )
356
+
357
+ def get_program_data_point(
358
+ self, *, pid: str | None = None, legacy_name: str | None = None, state_path: str | None = None
359
+ ) -> ProgramDpType | None:
360
+ """
361
+ Return a program data point by ID or legacy name.
362
+
363
+ Args:
364
+ ----
365
+ pid: Program identifier
366
+ legacy_name: Legacy name of the program
367
+ state_path: State path of the program
368
+
369
+ Returns:
370
+ -------
371
+ Program data point or None if not found
372
+
373
+ """
374
+ if state_path and (pid := self._state_path_to_name.get(state_path)):
375
+ return self.get_program_data_point(pid=pid)
376
+
377
+ if pid and (program := self._program_data_points.get(pid)):
378
+ return program
379
+ if legacy_name:
380
+ for program in self._program_data_points.values():
381
+ if legacy_name in (program.button.legacy_name, program.switch.legacy_name):
382
+ return program
383
+ return None
384
+
385
+ async def get_system_variable(self, *, legacy_name: str) -> Any | None:
386
+ """
387
+ Get system variable value from the backend.
388
+
389
+ Args:
390
+ ----
391
+ legacy_name: Legacy name of the system variable
392
+
393
+ Returns:
394
+ -------
395
+ Current value of the system variable or None
396
+
397
+ """
398
+ if client := self._primary_client_provider.primary_client:
399
+ return await client.get_system_variable(name=legacy_name)
400
+ return None
401
+
402
+ def get_sysvar_data_point(
403
+ self, *, vid: str | None = None, legacy_name: str | None = None, state_path: str | None = None
404
+ ) -> GenericSysvarDataPointProtocol | None:
405
+ """
406
+ Return a system variable data point by ID or legacy name.
407
+
408
+ Args:
409
+ ----
410
+ vid: System variable identifier
411
+ legacy_name: Legacy name of the system variable
412
+ state_path: State path of the system variable
413
+
414
+ Returns:
415
+ -------
416
+ System variable data point or None if not found
417
+
418
+ """
419
+ if state_path and (vid := self._state_path_to_name.get(state_path)):
420
+ return self.get_sysvar_data_point(vid=vid)
421
+
422
+ if vid and (sysvar := self._sysvar_data_points.get(vid)):
423
+ return sysvar
424
+ if legacy_name:
425
+ for sysvar in self._sysvar_data_points.values():
426
+ if sysvar.legacy_name == legacy_name:
427
+ return sysvar
428
+ return None
429
+
430
+ async def init_hub(self) -> None:
431
+ """Initialize the hub by fetching program, sysvar, inbox, install mode, metrics, and connectivity data."""
432
+ _LOGGER.debug("INIT_HUB: Initializing hub for %s", self._central_info.name)
433
+ await self._hub.fetch_program_data(scheduled=True)
434
+ await self._hub.fetch_sysvar_data(scheduled=True)
435
+ await self._hub.fetch_inbox_data(scheduled=False)
436
+ await self._hub.init_install_mode()
437
+ self._hub.init_metrics()
438
+ self._hub.init_connectivity()
439
+
440
+ async def init_install_mode(self) -> Mapping[Interface, InstallModeDpType]:
441
+ """
442
+ Initialize install mode data points for all supported interfaces.
443
+
444
+ Creates data points, fetches initial state from backend, and publishes refresh event.
445
+ Returns a dict of InstallModeDpType by Interface.
446
+ """
447
+ return await self._hub.init_install_mode()
448
+
449
+ def publish_install_mode_refreshed(self) -> None:
450
+ """Publish HUB_REFRESHED event for install mode data points."""
451
+ self._hub.publish_install_mode_refreshed()
452
+
453
+ def remove_program_button(self, *, pid: str) -> None:
454
+ """
455
+ Remove a program button.
456
+
457
+ Args:
458
+ ----
459
+ pid: Program identifier
460
+
461
+ """
462
+ if (program_dp := self.get_program_data_point(pid=pid)) is not None:
463
+ program_dp.button.publish_device_removed_event()
464
+ program_dp.switch.publish_device_removed_event()
465
+ self._program_data_points.pop(pid, None)
466
+ self._state_path_to_name.pop(program_dp.button.state_path, None)
467
+ _LOGGER.debug(
468
+ "REMOVE_PROGRAM_BUTTON: Removed program %s from %s",
469
+ pid,
470
+ self._central_info.name,
471
+ )
472
+
473
+ def remove_sysvar_data_point(self, *, vid: str) -> None:
474
+ """
475
+ Remove a system variable data point.
476
+
477
+ Args:
478
+ ----
479
+ vid: System variable identifier
480
+
481
+ """
482
+ if (sysvar_dp := self.get_sysvar_data_point(vid=vid)) is not None:
483
+ sysvar_dp.publish_device_removed_event()
484
+ self._sysvar_data_points.pop(vid, None)
485
+ self._state_path_to_name.pop(sysvar_dp.state_path, None)
486
+
487
+ # Note: Event subscriptions are cleaned up via clear() when central stops
488
+
489
+ _LOGGER.debug(
490
+ "REMOVE_SYSVAR_DATA_POINT: Removed sysvar %s from %s",
491
+ vid,
492
+ self._central_info.name,
493
+ )
494
+
495
+ async def set_program_state(self, *, pid: str, state: bool) -> bool:
496
+ """
497
+ Set program state on the backend.
498
+
499
+ Args:
500
+ ----
501
+ pid: Program identifier
502
+ state: New program state
503
+
504
+ Returns:
505
+ -------
506
+ True if setting succeeded, False otherwise
507
+
508
+ """
509
+ if client := self._primary_client_provider.primary_client:
510
+ return await client.set_program_state(pid=pid, state=state)
511
+ return False
512
+
513
+ async def set_system_variable(self, *, legacy_name: str, value: Any) -> None:
514
+ """
515
+ Set system variable value on the backend.
516
+
517
+ Args:
518
+ ----
519
+ legacy_name: Legacy name of the system variable
520
+ value: New value
521
+
522
+ """
523
+ if dp := self.get_sysvar_data_point(legacy_name=legacy_name):
524
+ await dp.send_variable(value=value)
525
+ else:
526
+ _LOGGER.error(
527
+ i18n.tr(
528
+ key="log.central.set_system_variable.not_found",
529
+ legacy_name=legacy_name,
530
+ name=self._central_info.name,
531
+ )
532
+ )