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,436 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Metadata handler.
5
+
6
+ Handles metadata, renaming, rooms, functions, install mode, inbox devices,
7
+ and service messages operations.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from typing import TYPE_CHECKING, Any, Final, cast
14
+
15
+ from aiohomematic import i18n
16
+ from aiohomematic.client.handlers.base import BaseHandler
17
+ from aiohomematic.const import InboxDeviceData, Interface, ServiceMessageData, ServiceMessageType, SystemUpdateData
18
+ from aiohomematic.decorators import inspector
19
+ from aiohomematic.exceptions import BaseHomematicException, ClientException
20
+ from aiohomematic.interfaces import MetadataOperationsProtocol
21
+ from aiohomematic.support import extract_exc_args
22
+
23
+ if TYPE_CHECKING:
24
+ from aiohomematic.client import AioJsonRpcAioHttpClient, BaseRpcProxy
25
+ from aiohomematic.interfaces import ClientDependenciesProtocol
26
+
27
+ _LOGGER: Final = logging.getLogger(__name__)
28
+
29
+
30
+ class MetadataHandler(BaseHandler, MetadataOperationsProtocol):
31
+ """
32
+ Handler for metadata and system information operations.
33
+
34
+ Implements MetadataOperationsProtocol protocol for ISP-compliant client operations.
35
+
36
+ Handles:
37
+ - Metadata read/write operations
38
+ - Device and channel renaming
39
+ - Room and function queries
40
+ - Install mode operations
41
+ - Inbox device management
42
+ - Service message queries
43
+ - System update information
44
+ - ReGa ID lookups
45
+ """
46
+
47
+ __slots__ = (
48
+ "_has_functions",
49
+ "_has_inbox_devices",
50
+ "_has_install_mode",
51
+ "_has_metadata",
52
+ "_has_rega_id_lookup",
53
+ "_has_rename",
54
+ "_has_rooms",
55
+ "_has_service_messages",
56
+ "_has_system_update_info",
57
+ )
58
+
59
+ def __init__(
60
+ self,
61
+ *,
62
+ client_deps: ClientDependenciesProtocol,
63
+ interface: Interface,
64
+ interface_id: str,
65
+ json_rpc_client: AioJsonRpcAioHttpClient,
66
+ proxy: BaseRpcProxy,
67
+ proxy_read: BaseRpcProxy,
68
+ has_functions: bool,
69
+ has_inbox_devices: bool,
70
+ has_install_mode: bool,
71
+ has_metadata: bool,
72
+ has_rega_id_lookup: bool,
73
+ has_rename: bool,
74
+ has_rooms: bool,
75
+ has_service_messages: bool,
76
+ has_system_update_info: bool,
77
+ ) -> None:
78
+ """Initialize the metadata handler."""
79
+ super().__init__(
80
+ client_deps=client_deps,
81
+ interface=interface,
82
+ interface_id=interface_id,
83
+ json_rpc_client=json_rpc_client,
84
+ proxy=proxy,
85
+ proxy_read=proxy_read,
86
+ )
87
+ self._has_functions: Final = has_functions
88
+ self._has_inbox_devices: Final = has_inbox_devices
89
+ self._has_install_mode: Final = has_install_mode
90
+ self._has_metadata: Final = has_metadata
91
+ self._has_rega_id_lookup: Final = has_rega_id_lookup
92
+ self._has_rename: Final = has_rename
93
+ self._has_rooms: Final = has_rooms
94
+ self._has_service_messages: Final = has_service_messages
95
+ self._has_system_update_info: Final = has_system_update_info
96
+
97
+ @inspector(re_raise=False)
98
+ async def accept_device_in_inbox(self, *, device_address: str) -> bool:
99
+ """
100
+ Accept a device from the CCU inbox, completing its pairing process.
101
+
102
+ The inbox contains newly paired devices waiting for user confirmation.
103
+ Accepting moves the device to the active device list.
104
+
105
+ Args:
106
+ device_address: SGTIN or address of the inbox device.
107
+
108
+ Returns:
109
+ True if accepted successfully, False if not supported or failed.
110
+
111
+ """
112
+ if not self._has_inbox_devices:
113
+ _LOGGER.debug("ACCEPT_DEVICE_IN_INBOX: Not supported by client for %s", self._interface_id)
114
+ return False
115
+
116
+ return await self._json_rpc_client.accept_device_in_inbox(device_address=device_address)
117
+
118
+ @inspector(re_raise=False, no_raise_return={})
119
+ async def get_all_functions(self) -> dict[str, set[str]]:
120
+ """
121
+ Return function assignments for all channels.
122
+
123
+ Functions are user-defined groupings in the CCU (e.g., "Lighting", "Heating").
124
+ Maps each channel's ReGa ID to its assigned functions.
125
+
126
+ Returns:
127
+ Dict mapping channel address to set of function names.
128
+
129
+ """
130
+ if not self._has_functions:
131
+ _LOGGER.debug("GET_ALL_FUNCTIONS: Not supported by client for %s", self._interface_id)
132
+ return {}
133
+
134
+ functions: dict[str, set[str]] = {}
135
+ rega_ids_function = await self._json_rpc_client.get_all_channel_rega_ids_function()
136
+ for address, rega_id in self._client_deps.cache_coordinator.device_details.device_channel_rega_ids.items():
137
+ if sections := rega_ids_function.get(rega_id):
138
+ if address not in functions:
139
+ functions[address] = set()
140
+ functions[address].update(sections)
141
+ return functions
142
+
143
+ @inspector(re_raise=False, no_raise_return={})
144
+ async def get_all_rooms(self) -> dict[str, set[str]]:
145
+ """
146
+ Return room assignments for all channels.
147
+
148
+ Rooms are user-defined location groupings in the CCU. Maps each
149
+ channel's ReGa ID to its assigned rooms.
150
+
151
+ Returns:
152
+ Dict mapping channel address to set of room names.
153
+
154
+ """
155
+ if not self._has_rooms:
156
+ _LOGGER.debug("GET_ALL_ROOMS: Not supported by client for %s", self._interface_id)
157
+ return {}
158
+
159
+ rooms: dict[str, set[str]] = {}
160
+ rega_ids_room = await self._json_rpc_client.get_all_channel_rega_ids_room()
161
+ for address, rega_id in self._client_deps.cache_coordinator.device_details.device_channel_rega_ids.items():
162
+ if names := rega_ids_room.get(rega_id):
163
+ if address not in rooms:
164
+ rooms[address] = set()
165
+ rooms[address].update(names)
166
+ return rooms
167
+
168
+ @inspector(re_raise=False, no_raise_return=())
169
+ async def get_inbox_devices(self) -> tuple[InboxDeviceData, ...]:
170
+ """
171
+ Return devices awaiting user acceptance in the CCU inbox.
172
+
173
+ After pairing, HmIP devices appear in the inbox until explicitly
174
+ accepted. Each entry contains device type, address, and SGTIN.
175
+
176
+ Returns:
177
+ Tuple of InboxDeviceData dicts for pending devices.
178
+
179
+ """
180
+ if not self._has_inbox_devices:
181
+ _LOGGER.debug("GET_INBOX_DEVICES: Not supported by client for %s", self._interface_id)
182
+ return ()
183
+
184
+ return await self._json_rpc_client.get_inbox_devices()
185
+
186
+ @inspector
187
+ async def get_install_mode(self) -> int:
188
+ """
189
+ Return remaining seconds in pairing/install mode.
190
+
191
+ Install mode allows new devices to be paired with the CCU. Uses JSON-RPC
192
+ for HmIP-RF interface and XML-RPC for BidCos interfaces.
193
+
194
+ Returns:
195
+ Remaining seconds, or 0 if install mode is off or unsupported.
196
+
197
+ Raises:
198
+ ClientException: If the RPC call fails.
199
+
200
+ """
201
+ if not self._has_install_mode:
202
+ _LOGGER.debug("GET_INSTALL_MODE: Not supported by client for %s", self._interface_id)
203
+ return 0
204
+
205
+ try:
206
+ # HmIP-RF uses JSON-RPC, BidCos-RF uses XML-RPC
207
+ if self._interface == Interface.HMIP_RF:
208
+ return await self._json_rpc_client.get_install_mode(interface=self._interface)
209
+
210
+ if (remaining_time := await self._proxy.getInstallMode()) is not None:
211
+ return int(remaining_time)
212
+ except BaseHomematicException as bhexc:
213
+ raise ClientException(
214
+ i18n.tr(
215
+ key="exception.client.get_install_mode.failed",
216
+ interface_id=self._interface_id,
217
+ reason=extract_exc_args(exc=bhexc),
218
+ )
219
+ ) from bhexc
220
+ return 0
221
+
222
+ @inspector
223
+ async def get_metadata(self, *, address: str, data_id: str) -> dict[str, Any]:
224
+ """
225
+ Return backend metadata for a device or channel.
226
+
227
+ Metadata is key-value storage attached to Homematic objects, used by
228
+ some backends for configuration data.
229
+
230
+ Args:
231
+ address: Device or channel address.
232
+ data_id: Metadata key identifier.
233
+
234
+ Returns:
235
+ Metadata value dict, or empty dict if unsupported.
236
+
237
+ Raises:
238
+ ClientException: If the RPC call fails.
239
+
240
+ """
241
+ if not self._has_metadata:
242
+ _LOGGER.debug("GET_METADATA: Not supported by client for %s", self._interface_id)
243
+ return {}
244
+
245
+ try:
246
+ return cast(dict[str, Any], await self._proxy.getMetadata(address, data_id))
247
+ except BaseHomematicException as bhexc:
248
+ raise ClientException(
249
+ i18n.tr(
250
+ key="exception.client.get_metadata.failed",
251
+ address=address,
252
+ data_id=data_id,
253
+ reason=extract_exc_args(exc=bhexc),
254
+ )
255
+ ) from bhexc
256
+
257
+ @inspector(re_raise=False)
258
+ async def get_rega_id_by_address(self, *, address: str) -> int | None:
259
+ """
260
+ Return the ReGaHSS internal ID for an address.
261
+
262
+ ReGa IDs are used by the CCU's scripting engine (ReGaHSS) to identify
263
+ devices and channels. Required for rename operations and room/function
264
+ lookups.
265
+
266
+ Args:
267
+ address: Device or channel address.
268
+
269
+ Returns:
270
+ ReGa ID integer, or None if not found or unsupported.
271
+
272
+ """
273
+ if not self._has_rega_id_lookup:
274
+ _LOGGER.debug("GET_REGA_ID_BY_ADDRESS: Not supported by client for %s", self._interface_id)
275
+ return None
276
+
277
+ return await self._json_rpc_client.get_rega_id_by_address(address=address)
278
+
279
+ @inspector(re_raise=False, no_raise_return=())
280
+ async def get_service_messages(
281
+ self,
282
+ *,
283
+ message_type: ServiceMessageType | None = None,
284
+ ) -> tuple[ServiceMessageData, ...]:
285
+ """
286
+ Get all active service messages from the backend.
287
+
288
+ Args:
289
+ message_type: Filter by message type. If None, return all messages.
290
+
291
+ """
292
+ if not self._has_service_messages:
293
+ _LOGGER.debug("GET_SERVICE_MESSAGES: Not supported by client for %s", self._interface_id)
294
+ return ()
295
+
296
+ return await self._json_rpc_client.get_service_messages(message_type=message_type)
297
+
298
+ @inspector(re_raise=False)
299
+ async def get_system_update_info(self) -> SystemUpdateData | None:
300
+ """
301
+ Return CCU firmware update availability status.
302
+
303
+ Checks if a newer CCU firmware version is available for download.
304
+
305
+ Returns:
306
+ SystemUpdateData with version info, or None if unsupported/unavailable.
307
+
308
+ """
309
+ if not self._has_system_update_info:
310
+ _LOGGER.debug("GET_SYSTEM_UPDATE_INFO: Not supported by client for %s", self._interface_id)
311
+ return None
312
+
313
+ return await self._json_rpc_client.get_system_update_info()
314
+
315
+ @inspector(re_raise=False)
316
+ async def rename_channel(self, *, rega_id: int, new_name: str) -> bool:
317
+ """
318
+ Rename a channel in the CCU's ReGaHSS database.
319
+
320
+ Args:
321
+ rega_id: ReGaHSS internal ID of the channel.
322
+ new_name: New display name for the channel.
323
+
324
+ Returns:
325
+ True if renamed successfully, False if unsupported or failed.
326
+
327
+ """
328
+ if not self._has_rename:
329
+ _LOGGER.debug("RENAME_CHANNEL: Not supported by client for %s", self._interface_id)
330
+ return False
331
+
332
+ return await self._json_rpc_client.rename_channel(rega_id=rega_id, new_name=new_name)
333
+
334
+ @inspector(re_raise=False)
335
+ async def rename_device(self, *, rega_id: int, new_name: str) -> bool:
336
+ """
337
+ Rename a device in the CCU's ReGaHSS database.
338
+
339
+ Args:
340
+ rega_id: ReGaHSS internal ID of the device.
341
+ new_name: New display name for the device.
342
+
343
+ Returns:
344
+ True if renamed successfully, False if unsupported or failed.
345
+
346
+ """
347
+ if not self._has_rename:
348
+ _LOGGER.debug("RENAME_DEVICE: Not supported by client for %s", self._interface_id)
349
+ return False
350
+
351
+ return await self._json_rpc_client.rename_device(rega_id=rega_id, new_name=new_name)
352
+
353
+ @inspector
354
+ async def set_install_mode(
355
+ self,
356
+ *,
357
+ on: bool = True,
358
+ time: int = 60,
359
+ mode: int = 1,
360
+ device_address: str | None = None,
361
+ ) -> bool:
362
+ """
363
+ Set the install mode on the backend.
364
+
365
+ Args:
366
+ on: Enable or disable install mode.
367
+ time: Duration in seconds (default 60).
368
+ mode: Mode 1=normal, 2=set all ROAMING devices into install mode.
369
+ device_address: Optional device address/SGTIN to limit pairing.
370
+
371
+ Returns:
372
+ True if successful.
373
+
374
+ """
375
+ if not self._has_install_mode:
376
+ _LOGGER.debug("SET_INSTALL_MODE: Not supported by client for %s", self._interface_id)
377
+ return False
378
+
379
+ try:
380
+ # HmIP-RF uses JSON-RPC setInstallModeHmIP, BidCos-RF uses XML-RPC
381
+ if self._interface == Interface.HMIP_RF:
382
+ return await self._json_rpc_client.set_install_mode_hmip(
383
+ interface=self._interface,
384
+ on=on,
385
+ time=time,
386
+ device_address=device_address,
387
+ )
388
+
389
+ if device_address:
390
+ await self._proxy.setInstallMode(on, time, device_address)
391
+ else:
392
+ await self._proxy.setInstallMode(on, time, mode)
393
+ except BaseHomematicException as bhexc:
394
+ raise ClientException(
395
+ i18n.tr(
396
+ key="exception.client.set_install_mode.failed",
397
+ interface_id=self._interface_id,
398
+ reason=extract_exc_args(exc=bhexc),
399
+ )
400
+ ) from bhexc
401
+ else:
402
+ return True
403
+
404
+ @inspector
405
+ async def set_metadata(self, *, address: str, data_id: str, value: dict[str, Any]) -> dict[str, Any]:
406
+ """
407
+ Write metadata for a device or channel.
408
+
409
+ Args:
410
+ address: Device or channel address.
411
+ data_id: Metadata key identifier.
412
+ value: Metadata value dict to store.
413
+
414
+ Returns:
415
+ Result dict from the backend, or empty dict if unsupported.
416
+
417
+ Raises:
418
+ ClientException: If the RPC call fails.
419
+
420
+ """
421
+ if not self._has_metadata:
422
+ _LOGGER.debug("SET_METADATA: Not supported by client for %s", self._interface_id)
423
+ return {}
424
+
425
+ try:
426
+ return cast(dict[str, Any], await self._proxy.setMetadata(address, data_id, value))
427
+ except BaseHomematicException as bhexc:
428
+ raise ClientException(
429
+ i18n.tr(
430
+ key="exception.client.set_metadata.failed",
431
+ address=address,
432
+ data_id=data_id,
433
+ value=value,
434
+ reason=extract_exc_args(exc=bhexc),
435
+ )
436
+ ) from bhexc
@@ -0,0 +1,144 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Program handler.
5
+
6
+ Handles program execution and state management.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import TYPE_CHECKING, Final
13
+
14
+ from aiohomematic.client.handlers.base import BaseHandler
15
+ from aiohomematic.const import DescriptionMarker, ProgramData
16
+ from aiohomematic.decorators import inspector
17
+ from aiohomematic.interfaces import ProgramOperationsProtocol
18
+ from aiohomematic.property_decorators import DelegatedProperty
19
+
20
+ if TYPE_CHECKING:
21
+ from aiohomematic.client import AioJsonRpcAioHttpClient, BaseRpcProxy
22
+ from aiohomematic.const import Interface
23
+ from aiohomematic.interfaces import ClientDependenciesProtocol
24
+
25
+ _LOGGER: Final = logging.getLogger(__name__)
26
+
27
+
28
+ class ProgramHandler(BaseHandler, ProgramOperationsProtocol):
29
+ """
30
+ Handler for program operations.
31
+
32
+ Implements ProgramOperationsProtocol protocol for ISP-compliant client operations.
33
+
34
+ Handles:
35
+ - Getting all programs
36
+ - Executing programs
37
+ - Setting program state
38
+ - Checking program IDs
39
+ """
40
+
41
+ __slots__ = ("_has_programs",)
42
+
43
+ def __init__(
44
+ self,
45
+ *,
46
+ client_deps: ClientDependenciesProtocol,
47
+ interface: Interface,
48
+ interface_id: str,
49
+ json_rpc_client: AioJsonRpcAioHttpClient,
50
+ proxy: BaseRpcProxy,
51
+ proxy_read: BaseRpcProxy,
52
+ has_programs: bool,
53
+ ) -> None:
54
+ """Initialize the program handler."""
55
+ super().__init__(
56
+ client_deps=client_deps,
57
+ interface=interface,
58
+ interface_id=interface_id,
59
+ json_rpc_client=json_rpc_client,
60
+ proxy=proxy,
61
+ proxy_read=proxy_read,
62
+ )
63
+ self._has_programs: Final = has_programs
64
+
65
+ has_programs: Final = DelegatedProperty[bool](path="_has_programs")
66
+
67
+ @inspector
68
+ async def execute_program(self, *, pid: str) -> bool:
69
+ """
70
+ Trigger execution of a CCU program.
71
+
72
+ CCU programs are user-defined scripts created in the CCU's web interface.
73
+ This method triggers immediate execution of the program.
74
+
75
+ Args:
76
+ pid: Program ID (ReGa internal identifier).
77
+
78
+ Returns:
79
+ True if execution was triggered, False if unsupported.
80
+
81
+ """
82
+ if not self._has_programs:
83
+ _LOGGER.debug("EXECUTE_PROGRAM: Not supported by client for %s", self._interface_id)
84
+ return False
85
+
86
+ return await self._json_rpc_client.execute_program(pid=pid)
87
+
88
+ @inspector(re_raise=False)
89
+ async def get_all_programs(
90
+ self,
91
+ *,
92
+ markers: tuple[DescriptionMarker | str, ...],
93
+ ) -> tuple[ProgramData, ...]:
94
+ """
95
+ Return all CCU programs matching the given markers.
96
+
97
+ Programs can be filtered by markers in their description field. Only
98
+ programs containing at least one of the specified markers are returned.
99
+
100
+ Args:
101
+ markers: Tuple of DescriptionMarker values or strings to filter by.
102
+
103
+ Returns:
104
+ Tuple of ProgramData dicts containing id, name, description, etc.
105
+
106
+ """
107
+ if not self._has_programs:
108
+ _LOGGER.debug("GET_ALL_PROGRAMS: Not supported by client for %s", self._interface_id)
109
+ return ()
110
+
111
+ return await self._json_rpc_client.get_all_programs(markers=markers)
112
+
113
+ @inspector
114
+ async def has_program_ids(self, *, rega_id: int) -> bool:
115
+ """
116
+ Check if a channel is used in any CCU programs.
117
+
118
+ Args:
119
+ rega_id: ReGaHSS internal ID of the channel to check.
120
+
121
+ Returns:
122
+ True if the channel is referenced in at least one program.
123
+
124
+ """
125
+ if not self._has_programs:
126
+ _LOGGER.debug("HAS_PROGRAM_IDS: Not supported by client for %s", self._interface_id)
127
+ return False
128
+
129
+ return await self._json_rpc_client.has_program_ids(rega_id=rega_id)
130
+
131
+ @inspector
132
+ async def set_program_state(self, *, pid: str, state: bool) -> bool:
133
+ """
134
+ Enable or disable a CCU program.
135
+
136
+ Args:
137
+ pid: Program ID (ReGa internal identifier).
138
+ state: True to enable, False to disable the program.
139
+
140
+ Returns:
141
+ True if the state was changed successfully.
142
+
143
+ """
144
+ return await self._json_rpc_client.set_program_state(pid=pid, state=state)
@@ -0,0 +1,100 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ System variable handler.
5
+
6
+ Handles system variable CRUD operations.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import Any, Final
13
+
14
+ from aiohomematic.client.handlers.base import BaseHandler
15
+ from aiohomematic.const import DescriptionMarker, SystemVariableData
16
+ from aiohomematic.decorators import inspector
17
+ from aiohomematic.interfaces import SystemVariableOperationsProtocol
18
+
19
+ _LOGGER: Final = logging.getLogger(__name__)
20
+
21
+
22
+ class SystemVariableHandler(BaseHandler, SystemVariableOperationsProtocol):
23
+ """
24
+ Handler for system variable operations.
25
+
26
+ Implements SystemVariableOperationsProtocol protocol for ISP-compliant client operations.
27
+
28
+ Handles:
29
+ - Getting all system variables
30
+ - Getting single system variable
31
+ - Setting system variables
32
+ - Deleting system variables
33
+ """
34
+
35
+ __slots__ = ()
36
+
37
+ @inspector
38
+ async def delete_system_variable(self, *, name: str) -> bool:
39
+ """
40
+ Delete a system variable from the CCU.
41
+
42
+ Args:
43
+ name: Name of the system variable to delete.
44
+
45
+ Returns:
46
+ True if deleted successfully.
47
+
48
+ """
49
+ return await self._json_rpc_client.delete_system_variable(name=name)
50
+
51
+ @inspector(re_raise=False)
52
+ async def get_all_system_variables(
53
+ self,
54
+ *,
55
+ markers: tuple[DescriptionMarker | str, ...],
56
+ ) -> tuple[SystemVariableData, ...] | None:
57
+ """
58
+ Return all CCU system variables matching the given markers.
59
+
60
+ System variables are global variables stored on the CCU that can be
61
+ used in programs and scripts. Variables can be filtered by markers
62
+ in their description field.
63
+
64
+ Args:
65
+ markers: Tuple of DescriptionMarker values or strings to filter by.
66
+
67
+ Returns:
68
+ Tuple of SystemVariableData dicts, or None on error.
69
+
70
+ """
71
+ return await self._json_rpc_client.get_all_system_variables(markers=markers)
72
+
73
+ @inspector
74
+ async def get_system_variable(self, *, name: str) -> Any:
75
+ """
76
+ Return the current value of a system variable.
77
+
78
+ Args:
79
+ name: Name of the system variable.
80
+
81
+ Returns:
82
+ Current value (type depends on variable definition).
83
+
84
+ """
85
+ return await self._json_rpc_client.get_system_variable(name=name)
86
+
87
+ @inspector(measure_performance=True)
88
+ async def set_system_variable(self, *, legacy_name: str, value: Any) -> bool:
89
+ """
90
+ Set the value of a system variable.
91
+
92
+ Args:
93
+ legacy_name: Original name of the system variable.
94
+ value: New value to set (must match variable's type definition).
95
+
96
+ Returns:
97
+ True if the value was set successfully.
98
+
99
+ """
100
+ return await self._json_rpc_client.set_system_variable(legacy_name=legacy_name, value=value)