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,772 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Builder pattern for CentralConfig with fluent interface.
5
+
6
+ This module provides a builder class that offers step-by-step configuration
7
+ with method chaining, early validation, and preset configurations.
8
+
9
+ Example:
10
+ # Simple CCU setup
11
+ config = (
12
+ CentralConfigBuilder()
13
+ .with_name(name="my-ccu")
14
+ .with_host(host="192.168.1.100")
15
+ .with_credentials(username="Admin", password="secret")
16
+ .add_hmip_interface()
17
+ .add_bidcos_rf_interface()
18
+ .build()
19
+ )
20
+
21
+ # Using preset
22
+ config = (
23
+ CentralConfigBuilder.for_ccu(host="192.168.1.100")
24
+ .with_credentials(username="Admin", password="secret")
25
+ .with_tls(enabled=True)
26
+ .build()
27
+ )
28
+
29
+ Public API of this module is defined by __all__.
30
+
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from dataclasses import dataclass
36
+ from typing import TYPE_CHECKING, Any, Self
37
+
38
+ from aiohomematic.central import CentralConfig
39
+ from aiohomematic.client import InterfaceConfig
40
+ from aiohomematic.const import (
41
+ DEFAULT_DELAY_NEW_DEVICE_CREATION,
42
+ DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK,
43
+ DEFAULT_ENABLE_PROGRAM_SCAN,
44
+ DEFAULT_ENABLE_SYSVAR_SCAN,
45
+ DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS,
46
+ DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH,
47
+ DEFAULT_LOCALE,
48
+ DEFAULT_MAX_READ_WORKERS,
49
+ DEFAULT_OPTIONAL_SETTINGS,
50
+ DEFAULT_PROGRAM_MARKERS,
51
+ DEFAULT_SCHEDULE_TIMER_CONFIG,
52
+ DEFAULT_STORAGE_DIRECTORY,
53
+ DEFAULT_SYSVAR_MARKERS,
54
+ DEFAULT_TIMEOUT_CONFIG,
55
+ DEFAULT_TLS,
56
+ DEFAULT_UN_IGNORES,
57
+ DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE,
58
+ DEFAULT_VERIFY_TLS,
59
+ PORT_ANY,
60
+ DescriptionMarker,
61
+ Interface,
62
+ OptionalSettings,
63
+ ScheduleTimerConfig,
64
+ TimeoutConfig,
65
+ get_interface_default_port,
66
+ get_json_rpc_default_port,
67
+ )
68
+
69
+ if TYPE_CHECKING:
70
+ from aiohttp import ClientSession
71
+
72
+ __all__ = ["CentralConfigBuilder", "ValidationError"]
73
+
74
+
75
+ @dataclass(frozen=True, slots=True)
76
+ class ValidationError:
77
+ """Validation error with field name and message."""
78
+
79
+ field: str
80
+ message: str
81
+
82
+
83
+ class CentralConfigBuilder:
84
+ """
85
+ Builder for CentralConfig with fluent interface.
86
+
87
+ Provides step-by-step configuration with method chaining, early validation,
88
+ and preset configurations for common backends.
89
+
90
+ The builder enforces that required fields (name, host, username, password)
91
+ are set before building, and provides sensible defaults for all optional fields.
92
+
93
+ Example:
94
+ # Full configuration
95
+ config = (
96
+ CentralConfigBuilder()
97
+ .with_name(name="production-ccu")
98
+ .with_host(host="ccu.local")
99
+ .with_credentials(username="admin", password="secret")
100
+ .with_tls(enabled=True, verify=True)
101
+ .add_hmip_interface()
102
+ .add_bidcos_rf_interface()
103
+ .with_storage(directory="/var/lib/aiohomematic")
104
+ .with_programs(enabled=True)
105
+ .with_sysvars(enabled=True)
106
+ .build()
107
+ )
108
+
109
+ # Validate before building
110
+ builder = CentralConfigBuilder().with_name(name="test")
111
+ errors = builder.validate()
112
+ if errors:
113
+ for error in errors:
114
+ print(f"{error.field}: {error.message}")
115
+
116
+ """
117
+
118
+ __slots__ = (
119
+ # Required
120
+ "_name",
121
+ "_host",
122
+ "_username",
123
+ "_password",
124
+ # Interfaces
125
+ "_interfaces",
126
+ # Connection
127
+ "_tls",
128
+ "_verify_tls",
129
+ "_json_port",
130
+ "_client_session",
131
+ # Callback server
132
+ "_callback_host",
133
+ "_callback_port_xml_rpc",
134
+ "_default_callback_port_xml_rpc",
135
+ "_listen_ip_addr",
136
+ "_listen_port_xml_rpc",
137
+ # Features
138
+ "_enable_program_scan",
139
+ "_enable_sysvar_scan",
140
+ "_enable_device_firmware_check",
141
+ "_program_markers",
142
+ "_sysvar_markers",
143
+ # Storage
144
+ "_storage_directory",
145
+ # Advanced
146
+ "_central_id",
147
+ "_delay_new_device_creation",
148
+ "_ignore_custom_device_definition_models",
149
+ "_interfaces_requiring_periodic_refresh",
150
+ "_max_read_workers",
151
+ "_optional_settings",
152
+ "_schedule_timer_config",
153
+ "_start_direct",
154
+ "_timeout_config",
155
+ "_un_ignore_list",
156
+ "_use_group_channel_for_cover_state",
157
+ "_locale",
158
+ )
159
+
160
+ def __init__(self) -> None:
161
+ """Initialize builder with default values."""
162
+ # Required (no default)
163
+ self._name: str | None = None
164
+ self._host: str | None = None
165
+ self._username: str | None = None
166
+ self._password: str | None = None
167
+
168
+ # Interfaces
169
+ self._interfaces: list[tuple[Interface, int | None, str | None]] = []
170
+
171
+ # Connection
172
+ self._tls: bool = DEFAULT_TLS
173
+ self._verify_tls: bool = DEFAULT_VERIFY_TLS
174
+ self._json_port: int | None = None
175
+ self._client_session: ClientSession | None = None
176
+
177
+ # Callback server
178
+ self._callback_host: str | None = None
179
+ self._callback_port_xml_rpc: int | None = None
180
+ self._default_callback_port_xml_rpc: int = PORT_ANY
181
+ self._listen_ip_addr: str | None = None
182
+ self._listen_port_xml_rpc: int | None = None
183
+
184
+ # Features
185
+ self._enable_program_scan: bool = DEFAULT_ENABLE_PROGRAM_SCAN
186
+ self._enable_sysvar_scan: bool = DEFAULT_ENABLE_SYSVAR_SCAN
187
+ self._enable_device_firmware_check: bool = DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK
188
+ self._program_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_PROGRAM_MARKERS
189
+ self._sysvar_markers: tuple[DescriptionMarker | str, ...] = DEFAULT_SYSVAR_MARKERS
190
+
191
+ # Storage
192
+ self._storage_directory: str = DEFAULT_STORAGE_DIRECTORY
193
+
194
+ # Advanced
195
+ self._central_id: str | None = None
196
+ self._delay_new_device_creation: bool = DEFAULT_DELAY_NEW_DEVICE_CREATION
197
+ self._ignore_custom_device_definition_models: frozenset[str] = DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS
198
+ self._interfaces_requiring_periodic_refresh: frozenset[Interface] = (
199
+ DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH
200
+ )
201
+ self._max_read_workers: int = DEFAULT_MAX_READ_WORKERS
202
+ self._optional_settings: tuple[OptionalSettings | str, ...] = DEFAULT_OPTIONAL_SETTINGS
203
+ self._schedule_timer_config: ScheduleTimerConfig = DEFAULT_SCHEDULE_TIMER_CONFIG
204
+ self._start_direct: bool = False
205
+ self._timeout_config: TimeoutConfig = DEFAULT_TIMEOUT_CONFIG
206
+ self._un_ignore_list: frozenset[str] = DEFAULT_UN_IGNORES
207
+ self._use_group_channel_for_cover_state: bool = DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE
208
+ self._locale: str = DEFAULT_LOCALE
209
+
210
+ @classmethod
211
+ def for_ccu(cls, *, host: str, name: str = "ccu") -> Self:
212
+ """
213
+ Create builder preset for CCU3/CCU2 backends.
214
+
215
+ Pre-configures standard CCU interfaces (HMIP_RF, BIDCOS_RF).
216
+
217
+ Args:
218
+ host: CCU hostname or IP address.
219
+ name: Central unit name (default: "ccu").
220
+
221
+ Returns:
222
+ Pre-configured builder. Add credentials and call build().
223
+
224
+ Example:
225
+ config = (
226
+ CentralConfigBuilder.for_ccu(host="192.168.1.100")
227
+ .with_credentials(username="Admin", password="secret")
228
+ .build()
229
+ )
230
+
231
+ """
232
+ return cls().with_name(name=name).with_host(host=host).add_all_standard_interfaces()
233
+
234
+ @classmethod
235
+ def for_homegear(cls, *, host: str, name: str = "homegear", port: int | None = None) -> Self:
236
+ """
237
+ Create builder preset for Homegear backends.
238
+
239
+ Pre-configures BidCos-RF interface.
240
+
241
+ Args:
242
+ host: Homegear hostname or IP address.
243
+ name: Central unit name (default: "homegear").
244
+ port: Custom BidCos-RF port (default: 2001).
245
+
246
+ Returns:
247
+ Pre-configured builder. Add credentials and call build().
248
+
249
+ Example:
250
+ config = (
251
+ CentralConfigBuilder.for_homegear(host="192.168.1.50")
252
+ .with_credentials(username="homegear", password="secret")
253
+ .build()
254
+ )
255
+
256
+ """
257
+ return cls().with_name(name=name).with_host(host=host).add_bidcos_rf_interface(port=port)
258
+
259
+ def add_all_standard_interfaces(self) -> Self:
260
+ """
261
+ Add all standard CCU interfaces (HMIP_RF, BIDCOS_RF).
262
+
263
+ Returns:
264
+ Self for method chaining.
265
+
266
+ """
267
+ return self.add_hmip_interface().add_bidcos_rf_interface()
268
+
269
+ def add_bidcos_rf_interface(self, *, port: int | None = None) -> Self:
270
+ """
271
+ Add BidCos RF wireless interface.
272
+
273
+ Default ports: 2001 (plain), 42001 (TLS).
274
+
275
+ Args:
276
+ port: Custom port. Uses default if not specified.
277
+
278
+ Returns:
279
+ Self for method chaining.
280
+
281
+ """
282
+ return self.add_interface(interface=Interface.BIDCOS_RF, port=port)
283
+
284
+ def add_bidcos_wired_interface(self, *, port: int | None = None) -> Self:
285
+ """
286
+ Add BidCos wired interface.
287
+
288
+ Default ports: 2000 (plain), 42000 (TLS).
289
+
290
+ Args:
291
+ port: Custom port. Uses default if not specified.
292
+
293
+ Returns:
294
+ Self for method chaining.
295
+
296
+ """
297
+ return self.add_interface(interface=Interface.BIDCOS_WIRED, port=port)
298
+
299
+ def add_cuxd_interface(self) -> Self:
300
+ """
301
+ Add CUxD interface.
302
+
303
+ CUxD uses JSON-RPC only and does not require an XML-RPC port.
304
+
305
+ Returns:
306
+ Self for method chaining.
307
+
308
+ """
309
+ return self.add_interface(interface=Interface.CUXD, port=0)
310
+
311
+ def add_hmip_interface(self, *, port: int | None = None) -> Self:
312
+ """
313
+ Add HomematicIP wireless interface (HMIP_RF).
314
+
315
+ Default ports: 2010 (plain), 42010 (TLS).
316
+
317
+ Args:
318
+ port: Custom port. Uses default if not specified.
319
+
320
+ Returns:
321
+ Self for method chaining.
322
+
323
+ """
324
+ return self.add_interface(interface=Interface.HMIP_RF, port=port)
325
+
326
+ def add_interface(
327
+ self,
328
+ *,
329
+ interface: Interface,
330
+ port: int | None = None,
331
+ remote_path: str | None = None,
332
+ ) -> Self:
333
+ """
334
+ Add a Homematic interface.
335
+
336
+ Args:
337
+ interface: Interface type (HMIP_RF, BIDCOS_RF, etc.).
338
+ port: Custom port. If not specified, uses default port
339
+ (adjusted for TLS if enabled).
340
+ remote_path: Optional remote path for the interface.
341
+
342
+ Returns:
343
+ Self for method chaining.
344
+
345
+ """
346
+ self._interfaces.append((interface, port, remote_path))
347
+ return self
348
+
349
+ def add_virtual_devices_interface(self, *, port: int | None = None) -> Self:
350
+ """
351
+ Add virtual devices interface.
352
+
353
+ Default ports: 9292 (plain), 49292 (TLS).
354
+
355
+ Args:
356
+ port: Custom port. Uses default if not specified.
357
+
358
+ Returns:
359
+ Self for method chaining.
360
+
361
+ """
362
+ return self.add_interface(interface=Interface.VIRTUAL_DEVICES, port=port, remote_path="/groups")
363
+
364
+ def build(self) -> CentralConfig:
365
+ """
366
+ Build the CentralConfig instance.
367
+
368
+ Returns:
369
+ Configured CentralConfig ready for create_central().
370
+
371
+ Raises:
372
+ ValueError: If required configuration is missing or invalid.
373
+
374
+ """
375
+ if errors := self.validate():
376
+ error_msgs = [f"{e.field}: {e.message}" for e in errors]
377
+ raise ValueError(f"Invalid configuration: {', '.join(error_msgs)}") # i18n-exc: ignore
378
+
379
+ # Build interface configs with resolved ports
380
+ interface_configs: set[InterfaceConfig] = set()
381
+ for interface, port, remote_path in self._interfaces:
382
+ # Use explicit port if provided, otherwise get default
383
+ # port=0 is valid for JSON-RPC-only interfaces (CUxD, CCU-Jack)
384
+ resolved_port = port if port is not None else get_interface_default_port(interface=interface, tls=self._tls)
385
+ if resolved_port is not None:
386
+ config_kwargs: dict[str, Any] = {
387
+ "central_name": self._name,
388
+ "interface": interface,
389
+ "port": resolved_port,
390
+ }
391
+ if remote_path:
392
+ config_kwargs["remote_path"] = remote_path
393
+ interface_configs.add(InterfaceConfig(**config_kwargs))
394
+
395
+ # Determine central_id
396
+ central_id = self._central_id or f"{self._name}-{self._host}"
397
+
398
+ # Determine json_port
399
+ json_port = self._json_port or get_json_rpc_default_port(tls=self._tls)
400
+
401
+ return CentralConfig(
402
+ # Required
403
+ central_id=central_id,
404
+ host=self._host, # type: ignore[arg-type]
405
+ interface_configs=interface_configs,
406
+ name=self._name, # type: ignore[arg-type]
407
+ password=self._password, # type: ignore[arg-type]
408
+ username=self._username, # type: ignore[arg-type]
409
+ # Connection
410
+ client_session=self._client_session,
411
+ tls=self._tls,
412
+ verify_tls=self._verify_tls,
413
+ json_port=json_port,
414
+ # Callback
415
+ callback_host=self._callback_host,
416
+ callback_port_xml_rpc=self._callback_port_xml_rpc,
417
+ default_callback_port_xml_rpc=self._default_callback_port_xml_rpc,
418
+ listen_ip_addr=self._listen_ip_addr,
419
+ listen_port_xml_rpc=self._listen_port_xml_rpc,
420
+ # Features
421
+ enable_program_scan=self._enable_program_scan,
422
+ enable_sysvar_scan=self._enable_sysvar_scan,
423
+ enable_device_firmware_check=self._enable_device_firmware_check,
424
+ program_markers=self._program_markers,
425
+ sysvar_markers=self._sysvar_markers,
426
+ # Storage
427
+ storage_directory=self._storage_directory,
428
+ # Advanced
429
+ delay_new_device_creation=self._delay_new_device_creation,
430
+ ignore_custom_device_definition_models=self._ignore_custom_device_definition_models,
431
+ interfaces_requiring_periodic_refresh=self._interfaces_requiring_periodic_refresh,
432
+ max_read_workers=self._max_read_workers,
433
+ optional_settings=self._optional_settings,
434
+ schedule_timer_config=self._schedule_timer_config,
435
+ start_direct=self._start_direct,
436
+ timeout_config=self._timeout_config,
437
+ un_ignore_list=self._un_ignore_list,
438
+ use_group_channel_for_cover_state=self._use_group_channel_for_cover_state,
439
+ locale=self._locale,
440
+ )
441
+
442
+ def validate(self) -> list[ValidationError]:
443
+ """
444
+ Validate the current configuration.
445
+
446
+ Returns:
447
+ List of validation errors. Empty if configuration is valid.
448
+
449
+ """
450
+ errors: list[ValidationError] = []
451
+
452
+ if not self._name:
453
+ errors.append(ValidationError(field="name", message="Name is required"))
454
+
455
+ if not self._host:
456
+ errors.append(ValidationError(field="host", message="Host is required"))
457
+
458
+ if self._username is None:
459
+ errors.append(ValidationError(field="username", message="Username is required"))
460
+
461
+ if self._password is None:
462
+ errors.append(ValidationError(field="password", message="Password is required"))
463
+
464
+ if not self._interfaces:
465
+ errors.append(ValidationError(field="interfaces", message="At least one interface is required"))
466
+
467
+ return errors
468
+
469
+ def with_callback(
470
+ self,
471
+ *,
472
+ host: str | None = None,
473
+ port: int | None = None,
474
+ listen_ip: str | None = None,
475
+ listen_port: int | None = None,
476
+ ) -> Self:
477
+ """
478
+ Configure XML-RPC callback server settings.
479
+
480
+ The callback server receives events from the CCU. If not configured,
481
+ the system auto-detects appropriate values.
482
+
483
+ Args:
484
+ host: Callback host address reported to CCU. Auto-detected if None.
485
+ port: Callback port reported to CCU. Auto-assigned if None.
486
+ listen_ip: IP address to bind the server to. Uses host if None.
487
+ listen_port: Port to listen on. Uses port if None.
488
+
489
+ Returns:
490
+ Self for method chaining.
491
+
492
+ """
493
+ self._callback_host = host
494
+ self._callback_port_xml_rpc = port
495
+ self._listen_ip_addr = listen_ip
496
+ self._listen_port_xml_rpc = listen_port
497
+ return self
498
+
499
+ def with_central_id(self, *, central_id: str) -> Self:
500
+ """
501
+ Set custom central ID.
502
+
503
+ Args:
504
+ central_id: Unique ID for this central. Auto-generated as
505
+ "{name}-{host}" if not specified.
506
+
507
+ Returns:
508
+ Self for method chaining.
509
+
510
+ """
511
+ self._central_id = central_id
512
+ return self
513
+
514
+ def with_client_session(self, *, session: ClientSession) -> Self:
515
+ """
516
+ Provide a custom aiohttp ClientSession.
517
+
518
+ Args:
519
+ session: Existing aiohttp session to use for HTTP requests.
520
+ If not provided, a new session will be created.
521
+
522
+ Returns:
523
+ Self for method chaining.
524
+
525
+ """
526
+ self._client_session = session
527
+ return self
528
+
529
+ def with_credentials(self, *, username: str, password: str) -> Self:
530
+ """
531
+ Set authentication credentials (required).
532
+
533
+ Args:
534
+ username: CCU/Homegear username.
535
+ password: CCU/Homegear password.
536
+
537
+ Returns:
538
+ Self for method chaining.
539
+
540
+ """
541
+ self._username = username
542
+ self._password = password
543
+ return self
544
+
545
+ def with_firmware_check(self, *, enabled: bool = True) -> Self:
546
+ """
547
+ Enable device firmware update checking.
548
+
549
+ Args:
550
+ enabled: Enable periodic firmware update availability checks.
551
+
552
+ Returns:
553
+ Self for method chaining.
554
+
555
+ """
556
+ self._enable_device_firmware_check = enabled
557
+ return self
558
+
559
+ def with_host(self, *, host: str) -> Self:
560
+ """
561
+ Set the CCU/Homegear host address (required).
562
+
563
+ Args:
564
+ host: IP address or hostname of the CCU/Homegear backend.
565
+
566
+ Returns:
567
+ Self for method chaining.
568
+
569
+ Raises:
570
+ ValueError: If host is empty or whitespace-only.
571
+
572
+ """
573
+ if not host or not host.strip():
574
+ raise ValueError("Host cannot be empty") # i18n-exc: ignore
575
+ self._host = host.strip()
576
+ return self
577
+
578
+ def with_json_port(self, *, port: int) -> Self:
579
+ """
580
+ Set JSON-RPC port for ReGaHss communication.
581
+
582
+ Args:
583
+ port: JSON-RPC port (default: 80 plain, 443 TLS).
584
+
585
+ Returns:
586
+ Self for method chaining.
587
+
588
+ """
589
+ self._json_port = port
590
+ return self
591
+
592
+ def with_locale(self, *, locale: str) -> Self:
593
+ """
594
+ Set locale for translations.
595
+
596
+ Args:
597
+ locale: Locale code (e.g., "en", "de").
598
+
599
+ Returns:
600
+ Self for method chaining.
601
+
602
+ """
603
+ self._locale = locale
604
+ return self
605
+
606
+ def with_name(self, *, name: str) -> Self:
607
+ """
608
+ Set the central unit name (required).
609
+
610
+ Args:
611
+ name: Unique identifier for this central unit.
612
+ Used in logging, entity IDs, and storage paths.
613
+
614
+ Returns:
615
+ Self for method chaining.
616
+
617
+ Raises:
618
+ ValueError: If name is empty or whitespace-only.
619
+
620
+ """
621
+ if not name or not name.strip():
622
+ raise ValueError("Name cannot be empty") # i18n-exc: ignore
623
+ self._name = name.strip()
624
+ return self
625
+
626
+ def with_optional_settings(self, *, settings: tuple[OptionalSettings | str, ...]) -> Self:
627
+ """
628
+ Set optional feature settings.
629
+
630
+ Args:
631
+ settings: Tuple of OptionalSettings flags.
632
+
633
+ Returns:
634
+ Self for method chaining.
635
+
636
+ """
637
+ self._optional_settings = settings
638
+ return self
639
+
640
+ def with_programs(
641
+ self,
642
+ *,
643
+ enabled: bool = True,
644
+ markers: tuple[DescriptionMarker | str, ...] | None = None,
645
+ ) -> Self:
646
+ """
647
+ Configure CCU program scanning.
648
+
649
+ Args:
650
+ enabled: Enable program discovery and synchronization.
651
+ markers: Optional markers to filter programs by description content.
652
+
653
+ Returns:
654
+ Self for method chaining.
655
+
656
+ """
657
+ self._enable_program_scan = enabled
658
+ if markers is not None:
659
+ self._program_markers = markers
660
+ return self
661
+
662
+ def with_schedule_timer_config(self, *, config: ScheduleTimerConfig) -> Self:
663
+ """
664
+ Set custom scheduler timer configuration.
665
+
666
+ Args:
667
+ config: ScheduleTimerConfig with custom interval values.
668
+
669
+ Returns:
670
+ Self for method chaining.
671
+
672
+ """
673
+ self._schedule_timer_config = config
674
+ return self
675
+
676
+ def with_start_direct(self, *, enabled: bool = True) -> Self:
677
+ """
678
+ Enable direct start mode (skip some initialization).
679
+
680
+ Args:
681
+ enabled: Enable direct start mode.
682
+
683
+ Returns:
684
+ Self for method chaining.
685
+
686
+ """
687
+ self._start_direct = enabled
688
+ return self
689
+
690
+ def with_storage(self, *, directory: str) -> Self:
691
+ """
692
+ Configure persistent storage location.
693
+
694
+ Args:
695
+ directory: Path to storage directory for caches and descriptions.
696
+
697
+ Returns:
698
+ Self for method chaining.
699
+
700
+ """
701
+ self._storage_directory = directory
702
+ return self
703
+
704
+ def with_sysvars(
705
+ self,
706
+ *,
707
+ enabled: bool = True,
708
+ markers: tuple[DescriptionMarker | str, ...] | None = None,
709
+ ) -> Self:
710
+ """
711
+ Configure CCU system variable scanning.
712
+
713
+ Args:
714
+ enabled: Enable sysvar discovery and synchronization.
715
+ markers: Optional markers to filter sysvars by description content.
716
+
717
+ Returns:
718
+ Self for method chaining.
719
+
720
+ """
721
+ self._enable_sysvar_scan = enabled
722
+ if markers is not None:
723
+ self._sysvar_markers = markers
724
+ return self
725
+
726
+ def with_timeout_config(self, *, config: TimeoutConfig) -> Self:
727
+ """
728
+ Set custom timeout configuration.
729
+
730
+ Args:
731
+ config: TimeoutConfig with custom timeout values.
732
+
733
+ Returns:
734
+ Self for method chaining.
735
+
736
+ """
737
+ self._timeout_config = config
738
+ return self
739
+
740
+ def with_tls(self, *, enabled: bool = True, verify: bool = True) -> Self:
741
+ """
742
+ Configure TLS settings.
743
+
744
+ When TLS is enabled, interface ports automatically use TLS variants
745
+ (e.g., 2010 → 42010 for HMIP_RF).
746
+
747
+ Args:
748
+ enabled: Enable TLS encryption for all connections.
749
+ verify: Verify TLS certificates. Set to False for self-signed certs.
750
+ Note: Disabling verification reduces security.
751
+
752
+ Returns:
753
+ Self for method chaining.
754
+
755
+ """
756
+ self._tls = enabled
757
+ self._verify_tls = verify
758
+ return self
759
+
760
+ def with_un_ignore_list(self, *, parameters: frozenset[str]) -> Self:
761
+ """
762
+ Set parameters to un-ignore (make visible).
763
+
764
+ Args:
765
+ parameters: Frozenset of parameter names to un-ignore.
766
+
767
+ Returns:
768
+ Self for method chaining.
769
+
770
+ """
771
+ self._un_ignore_list = parameters
772
+ return self