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,187 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Error mapping helpers for RPC transports.
5
+
6
+ This module centralizes small, transport-agnostic utilities to turn the backend
7
+ errors into domain-specific exceptions with useful context. It is used by both
8
+ JSON-RPC and XML-RPC clients.
9
+
10
+ Key types and functions
11
+ - RpcContext: Lightweight context container that formats protocol/method/host
12
+ for readable error messages and logs.
13
+ - map_jsonrpc_error: Maps a JSON-RPC error object to an appropriate exception
14
+ (AuthFailure, InternalBackendException, ClientException).
15
+ - map_transport_error: Maps generic transport-level exceptions like OSError to
16
+ domain exceptions (NoConnectionException/ClientException).
17
+ - map_xmlrpc_fault: Maps XML-RPC faults to domain exceptions with context.
18
+ - sanitize_error_message: Sanitizes error messages to remove sensitive data.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from asyncio import TimeoutError as AsyncTimeoutError
24
+ from collections.abc import Mapping
25
+ from dataclasses import dataclass
26
+ import re
27
+ from typing import Any, Final
28
+
29
+ from aiohomematic.const import FailureReason
30
+ from aiohomematic.exceptions import (
31
+ AuthFailure,
32
+ CircuitBreakerOpenException,
33
+ ClientException,
34
+ InternalBackendException,
35
+ NoConnectionException,
36
+ )
37
+
38
+ # Patterns that may contain sensitive information
39
+ _SENSITIVE_PATTERNS: Final[tuple[tuple[str, str], ...]] = (
40
+ # IP addresses (IPv4)
41
+ (r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", "<ip-redacted>"),
42
+ # Hostnames with domains
43
+ (r"\b[a-zA-Z0-9][-a-zA-Z0-9]*(\.[a-zA-Z0-9][-a-zA-Z0-9]*)+\b", "<host-redacted>"),
44
+ # Session IDs (common patterns)
45
+ (r"['\"]?session[_-]?id['\"]?\s*[:=]\s*['\"]?[\w-]+['\"]?", "session_id=<redacted>"),
46
+ # Passwords in URLs or params
47
+ (r"['\"]?password['\"]?\s*[:=]\s*['\"][^'\"]*['\"]", "password=<redacted>"),
48
+ (r"['\"]?passwd['\"]?\s*[:=]\s*['\"][^'\"]*['\"]", "passwd=<redacted>"),
49
+ )
50
+
51
+
52
+ def sanitize_error_message(*, message: str) -> str:
53
+ """
54
+ Sanitize error message by removing potentially sensitive information.
55
+
56
+ Removes or masks:
57
+ - IP addresses
58
+ - Hostnames
59
+ - Session IDs
60
+ - Passwords
61
+
62
+ Args:
63
+ message: The error message to sanitize.
64
+
65
+ Returns:
66
+ Sanitized error message.
67
+
68
+ """
69
+ result = message
70
+ for pattern, replacement in _SENSITIVE_PATTERNS:
71
+ result = re.sub(pattern, replacement, result, flags=re.IGNORECASE)
72
+ return result
73
+
74
+
75
+ @dataclass(slots=True)
76
+ class RpcContext:
77
+ """
78
+ Context container for RPC operations.
79
+
80
+ Provides formatted output for error messages with optional sanitization
81
+ to protect sensitive information in logs.
82
+ """
83
+
84
+ protocol: str
85
+ method: str
86
+ host: str | None = None
87
+ interface: str | None = None
88
+ params: Mapping[str, Any] | None = None
89
+
90
+ def fmt(self, *, sanitize: bool = False) -> str:
91
+ """
92
+ Format context for error messages.
93
+
94
+ Args:
95
+ sanitize: If True, omit host information for security.
96
+
97
+ Returns:
98
+ Formatted context string.
99
+
100
+ """
101
+ parts: list[str] = [f"protocol={self.protocol}", f"method={self.method}"]
102
+ if self.interface:
103
+ parts.append(f"interface={self.interface}")
104
+ if self.host and not sanitize:
105
+ parts.append(f"host={self.host}")
106
+ return ", ".join(parts)
107
+
108
+ def fmt_sanitized(self) -> str:
109
+ """Format context with sensitive information redacted."""
110
+ return self.fmt(sanitize=True)
111
+
112
+
113
+ def map_jsonrpc_error(*, error: Mapping[str, Any], ctx: RpcContext) -> Exception:
114
+ """Map JSON-RPC error to exception."""
115
+ # JSON-RPC 2.0 like error: {code, message, data?}
116
+ code = int(error.get("code", 0))
117
+ message = str(error.get("message", ""))
118
+ # Enrich message with context
119
+ base_msg = f"{message} ({ctx.fmt()})"
120
+
121
+ # Map common codes
122
+ if message.startswith("access denied") or code in (401, -32001):
123
+ return AuthFailure(base_msg)
124
+ if "internal error" in message.lower() or code in (-32603, 500):
125
+ return InternalBackendException(base_msg)
126
+ # Generic client exception for others
127
+ return ClientException(base_msg)
128
+
129
+
130
+ def map_transport_error(*, exc: BaseException, ctx: RpcContext) -> Exception:
131
+ """Map transport error to exception."""
132
+ msg = f"{exc} ({ctx.fmt()})"
133
+ if isinstance(exc, OSError):
134
+ return NoConnectionException(msg)
135
+ return ClientException(msg)
136
+
137
+
138
+ def map_xmlrpc_fault(*, code: int, fault_string: str, ctx: RpcContext) -> Exception:
139
+ """Map XML-RPC fault to exception."""
140
+ # Enrich message with context
141
+ fault_msg = f"XMLRPC Fault {code}: {fault_string} ({ctx.fmt()})"
142
+ # Simple mappings
143
+ if "unauthorized" in fault_string.lower():
144
+ return AuthFailure(fault_msg)
145
+ if "internal" in fault_string.lower():
146
+ return InternalBackendException(fault_msg)
147
+ return ClientException(fault_msg)
148
+
149
+
150
+ def exception_to_failure_reason(*, exc: BaseException) -> FailureReason:
151
+ """
152
+ Map an exception to a FailureReason enum value.
153
+
154
+ This function translates exceptions into categorized failure reasons
155
+ that can be used by state machines and propagated to integrations.
156
+
157
+ Args:
158
+ exc: The exception to categorize.
159
+
160
+ Returns:
161
+ The appropriate FailureReason for the exception type.
162
+
163
+ Example:
164
+ ```python
165
+ try:
166
+ await client.login()
167
+ except BaseException as exc:
168
+ reason = exception_to_failure_reason(exc=exc)
169
+ state_machine.transition_to(
170
+ target=ClientState.FAILED,
171
+ reason=str(exc),
172
+ failure_reason=reason,
173
+ )
174
+ ```
175
+
176
+ """
177
+ if isinstance(exc, AuthFailure):
178
+ return FailureReason.AUTH
179
+ if isinstance(exc, NoConnectionException):
180
+ return FailureReason.NETWORK
181
+ if isinstance(exc, InternalBackendException):
182
+ return FailureReason.INTERNAL
183
+ if isinstance(exc, CircuitBreakerOpenException):
184
+ return FailureReason.CIRCUIT_BREAKER
185
+ if isinstance(exc, TimeoutError | AsyncTimeoutError):
186
+ return FailureReason.TIMEOUT
187
+ return FailureReason.UNKNOWN
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Backend implementations for different Homematic systems.
5
+
6
+ This package provides backend-specific implementations that abstract
7
+ the transport layer (XML-RPC, JSON-RPC) from the client business logic.
8
+
9
+ Public API
10
+ ----------
11
+ - BackendOperationsProtocol: Interface for all backend operations
12
+ - BackendCapabilities: Capability flags dataclass
13
+ - CcuBackend: CCU backend (XML-RPC + JSON-RPC)
14
+ - JsonCcuBackend: CCU-Jack backend (JSON-RPC only)
15
+ - HomegearBackend: Homegear backend (XML-RPC with extensions)
16
+ - create_backend: Factory function to create appropriate backend
17
+
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from aiohomematic.client.backends.capabilities import (
23
+ CCU_CAPABILITIES,
24
+ HOMEGEAR_CAPABILITIES,
25
+ JSON_CCU_CAPABILITIES,
26
+ BackendCapabilities,
27
+ )
28
+ from aiohomematic.client.backends.ccu import CcuBackend
29
+ from aiohomematic.client.backends.factory import create_backend
30
+ from aiohomematic.client.backends.homegear import HomegearBackend
31
+ from aiohomematic.client.backends.json_ccu import JsonCcuBackend
32
+ from aiohomematic.client.backends.protocol import BackendOperationsProtocol
33
+
34
+ __all__ = [
35
+ # Protocol
36
+ "BackendOperationsProtocol",
37
+ # Capabilities
38
+ "BackendCapabilities",
39
+ "CCU_CAPABILITIES",
40
+ "HOMEGEAR_CAPABILITIES",
41
+ "JSON_CCU_CAPABILITIES",
42
+ # Backends
43
+ "CcuBackend",
44
+ "HomegearBackend",
45
+ "JsonCcuBackend",
46
+ # Factory
47
+ "create_backend",
48
+ ]
@@ -0,0 +1,335 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ # pylint: disable=unnecessary-ellipsis
4
+ """
5
+ Base backend class with shared functionality.
6
+
7
+ Provides default implementations that return empty/False for unsupported
8
+ operations, allowing subclasses to only implement what they support.
9
+
10
+ Public API
11
+ ----------
12
+ - BaseBackend: Abstract base class for backend implementations
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+ import logging
19
+ from typing import TYPE_CHECKING, Any, Final
20
+
21
+ from aiohomematic.client.backends.capabilities import BackendCapabilities
22
+ from aiohomematic.const import (
23
+ BackupData,
24
+ CommandRxMode,
25
+ DescriptionMarker,
26
+ DeviceDescription,
27
+ DeviceDetail,
28
+ InboxDeviceData,
29
+ Interface,
30
+ ParameterData,
31
+ ParamsetKey,
32
+ ProgramData,
33
+ ServiceMessageData,
34
+ ServiceMessageType,
35
+ SystemInformation,
36
+ SystemUpdateData,
37
+ SystemVariableData,
38
+ )
39
+
40
+ if TYPE_CHECKING:
41
+ from aiohomematic.client.circuit_breaker import CircuitBreaker
42
+
43
+ __all__ = ["BaseBackend"]
44
+
45
+ _LOGGER: Final = logging.getLogger(__name__)
46
+
47
+
48
+ class BaseBackend(ABC):
49
+ """
50
+ Abstract base class for backend implementations.
51
+
52
+ Provides default implementations that return empty/False for
53
+ unsupported operations, allowing subclasses to only implement
54
+ what they support.
55
+ """
56
+
57
+ __slots__ = (
58
+ "_capabilities",
59
+ "_interface",
60
+ "_interface_id",
61
+ "_system_information",
62
+ )
63
+
64
+ def __init__(
65
+ self,
66
+ *,
67
+ interface: Interface,
68
+ interface_id: str,
69
+ capabilities: BackendCapabilities,
70
+ ) -> None:
71
+ """Initialize the base backend."""
72
+ self._interface: Final = interface
73
+ self._interface_id: Final = interface_id
74
+ self._capabilities = capabilities
75
+ self._system_information: SystemInformation
76
+
77
+ @property
78
+ def all_circuit_breakers_closed(self) -> bool:
79
+ """Return True if all circuit breakers are in closed state."""
80
+ return True
81
+
82
+ @property
83
+ def capabilities(self) -> BackendCapabilities:
84
+ """Return the capability flags for this backend."""
85
+ return self._capabilities
86
+
87
+ @property
88
+ def circuit_breaker(self) -> CircuitBreaker | None:
89
+ """Return the primary circuit breaker for metrics access."""
90
+ return None
91
+
92
+ @property
93
+ def interface(self) -> Interface:
94
+ """Return the interface type."""
95
+ return self._interface
96
+
97
+ @property
98
+ def interface_id(self) -> str:
99
+ """Return the interface identifier."""
100
+ return self._interface_id
101
+
102
+ @property
103
+ @abstractmethod
104
+ def model(self) -> str:
105
+ """Return the backend model name."""
106
+ ...
107
+
108
+ @property
109
+ def system_information(self) -> SystemInformation:
110
+ """Return system information."""
111
+ return self._system_information
112
+
113
+ async def accept_device_in_inbox(self, *, device_address: str) -> bool:
114
+ """Accept inbox device (unsupported by default)."""
115
+ return False
116
+
117
+ async def add_link(
118
+ self,
119
+ *,
120
+ sender_address: str,
121
+ receiver_address: str,
122
+ name: str,
123
+ description: str,
124
+ ) -> None:
125
+ """Add link (unsupported by default)."""
126
+
127
+ @abstractmethod
128
+ async def check_connection(self, *, handle_ping_pong: bool, caller_id: str | None = None) -> bool:
129
+ """Check if connection is alive."""
130
+ ...
131
+
132
+ async def create_backup_and_download(
133
+ self,
134
+ *,
135
+ max_wait_time: float = 300.0,
136
+ poll_interval: float = 5.0,
137
+ ) -> BackupData | None:
138
+ """Create backup (unsupported by default)."""
139
+ return None
140
+
141
+ @abstractmethod
142
+ async def deinit_proxy(self, *, init_url: str) -> None:
143
+ """De-initialize proxy."""
144
+ ...
145
+
146
+ async def delete_system_variable(self, *, name: str) -> bool:
147
+ """Delete system variable (unsupported by default)."""
148
+ return False
149
+
150
+ async def execute_program(self, *, pid: str) -> bool:
151
+ """Execute program (unsupported by default)."""
152
+ return False
153
+
154
+ async def get_all_device_data(self, *, interface: Interface) -> dict[str, Any] | None:
155
+ """Return all device data (unsupported by default)."""
156
+ return None
157
+
158
+ async def get_all_functions(self) -> dict[str, set[str]]:
159
+ """Return all functions (unsupported by default)."""
160
+ return {}
161
+
162
+ async def get_all_programs(self, *, markers: tuple[DescriptionMarker | str, ...]) -> tuple[ProgramData, ...]:
163
+ """Return all programs (unsupported by default)."""
164
+ return ()
165
+
166
+ async def get_all_rooms(self) -> dict[str, set[str]]:
167
+ """Return all rooms (unsupported by default)."""
168
+ return {}
169
+
170
+ async def get_all_system_variables(
171
+ self, *, markers: tuple[DescriptionMarker | str, ...]
172
+ ) -> tuple[SystemVariableData, ...] | None:
173
+ """Return all system variables (unsupported by default)."""
174
+ return None
175
+
176
+ @abstractmethod
177
+ async def get_device_description(self, *, address: str) -> DeviceDescription | None:
178
+ """Return device description for address."""
179
+ ...
180
+
181
+ async def get_device_details(self, *, addresses: tuple[str, ...] | None = None) -> list[DeviceDetail] | None:
182
+ """Return device details (unsupported by default)."""
183
+ return None
184
+
185
+ async def get_inbox_devices(self) -> tuple[InboxDeviceData, ...]:
186
+ """Return inbox devices (unsupported by default)."""
187
+ return ()
188
+
189
+ async def get_install_mode(self) -> int:
190
+ """Return install mode time (unsupported by default)."""
191
+ return 0
192
+
193
+ async def get_link_peers(self, *, address: str) -> tuple[str, ...]:
194
+ """Return link peers (unsupported by default)."""
195
+ return ()
196
+
197
+ async def get_links(self, *, address: str, flags: int) -> dict[str, Any]:
198
+ """Return links (unsupported by default)."""
199
+ return {}
200
+
201
+ async def get_metadata(self, *, address: str, data_id: str) -> dict[str, Any]:
202
+ """Return metadata (unsupported by default)."""
203
+ return {}
204
+
205
+ @abstractmethod
206
+ async def get_paramset(self, *, address: str, paramset_key: ParamsetKey | str) -> dict[str, Any]:
207
+ """Return paramset."""
208
+ ...
209
+
210
+ @abstractmethod
211
+ async def get_paramset_description(
212
+ self, *, address: str, paramset_key: ParamsetKey
213
+ ) -> dict[str, ParameterData] | None:
214
+ """Return paramset description."""
215
+ ...
216
+
217
+ async def get_rega_id_by_address(self, *, address: str) -> int | None:
218
+ """Return ReGa ID (unsupported by default)."""
219
+ return None
220
+
221
+ async def get_service_messages(
222
+ self, *, message_type: ServiceMessageType | None = None
223
+ ) -> tuple[ServiceMessageData, ...]:
224
+ """Return service messages (unsupported by default)."""
225
+ return ()
226
+
227
+ async def get_system_update_info(self) -> SystemUpdateData | None:
228
+ """Return system update info (unsupported by default)."""
229
+ return None
230
+
231
+ async def get_system_variable(self, *, name: str) -> Any:
232
+ """Return system variable value (unsupported by default)."""
233
+ return None
234
+
235
+ @abstractmethod
236
+ async def get_value(self, *, address: str, parameter: str) -> Any:
237
+ """Return parameter value."""
238
+ ...
239
+
240
+ async def has_program_ids(self, *, rega_id: int) -> bool:
241
+ """Check program IDs (unsupported by default)."""
242
+ return False
243
+
244
+ @abstractmethod
245
+ async def init_proxy(self, *, init_url: str, interface_id: str) -> None:
246
+ """Initialize proxy with callback URL."""
247
+ ...
248
+
249
+ @abstractmethod
250
+ async def initialize(self) -> None:
251
+ """Initialize the backend."""
252
+ ...
253
+
254
+ @abstractmethod
255
+ async def list_devices(self) -> tuple[DeviceDescription, ...] | None:
256
+ """Return all device descriptions."""
257
+ ...
258
+
259
+ @abstractmethod
260
+ async def put_paramset(
261
+ self,
262
+ *,
263
+ address: str,
264
+ paramset_key: ParamsetKey | str,
265
+ values: dict[str, Any],
266
+ rx_mode: CommandRxMode | None = None,
267
+ ) -> None:
268
+ """Set paramset values."""
269
+ ...
270
+
271
+ async def remove_link(self, *, sender_address: str, receiver_address: str) -> None:
272
+ """Remove link (unsupported by default)."""
273
+
274
+ async def rename_channel(self, *, rega_id: int, new_name: str) -> bool:
275
+ """Rename channel (unsupported by default)."""
276
+ return False
277
+
278
+ async def rename_device(self, *, rega_id: int, new_name: str) -> bool:
279
+ """Rename device (unsupported by default)."""
280
+ return False
281
+
282
+ async def report_value_usage(self, *, address: str, value_id: str, ref_counter: int) -> bool:
283
+ """Report value usage (unsupported by default)."""
284
+ return False
285
+
286
+ def reset_circuit_breakers(self) -> None:
287
+ """Reset all circuit breakers to closed state."""
288
+
289
+ async def set_install_mode(
290
+ self,
291
+ *,
292
+ on: bool = True,
293
+ time: int = 60,
294
+ mode: int = 1,
295
+ device_address: str | None = None,
296
+ ) -> bool:
297
+ """Set install mode (unsupported by default)."""
298
+ return False
299
+
300
+ async def set_metadata(self, *, address: str, data_id: str, value: dict[str, Any]) -> dict[str, Any]:
301
+ """Set metadata (unsupported by default)."""
302
+ return {}
303
+
304
+ async def set_program_state(self, *, pid: str, state: bool) -> bool:
305
+ """Set program state (unsupported by default)."""
306
+ return False
307
+
308
+ async def set_system_variable(self, *, name: str, value: Any) -> bool:
309
+ """Set system variable (unsupported by default)."""
310
+ return False
311
+
312
+ @abstractmethod
313
+ async def set_value(
314
+ self,
315
+ *,
316
+ address: str,
317
+ parameter: str,
318
+ value: Any,
319
+ rx_mode: CommandRxMode | None = None,
320
+ ) -> None:
321
+ """Set parameter value."""
322
+ ...
323
+
324
+ @abstractmethod
325
+ async def stop(self) -> None:
326
+ """Stop the backend."""
327
+ ...
328
+
329
+ async def trigger_firmware_update(self) -> bool:
330
+ """Trigger system firmware update (unsupported by default)."""
331
+ return False
332
+
333
+ async def update_device_firmware(self, *, device_address: str) -> bool:
334
+ """Update device firmware (unsupported by default)."""
335
+ return False
@@ -0,0 +1,138 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Backend capabilities dataclass.
5
+
6
+ Consolidates all capability flags into a single immutable structure,
7
+ replacing the 20+ properties spread across client classes.
8
+
9
+ Public API
10
+ ----------
11
+ - BackendCapabilities: Frozen dataclass with capability flags
12
+ - CCU_CAPABILITIES: Default capabilities for CCU backend
13
+ - JSON_CCU_CAPABILITIES: Default capabilities for CCU-Jack backend
14
+ - HOMEGEAR_CAPABILITIES: Default capabilities for Homegear backend
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from typing import Final
21
+
22
+ __all__ = [
23
+ "BackendCapabilities",
24
+ "CCU_CAPABILITIES",
25
+ "HOMEGEAR_CAPABILITIES",
26
+ "JSON_CCU_CAPABILITIES",
27
+ ]
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class BackendCapabilities:
32
+ """
33
+ Immutable capability flags for a backend.
34
+
35
+ Consolidates the capability properties from ClientCCU, ClientJsonCCU,
36
+ and ClientHomegear into a single dataclass. Backends declare their
37
+ capabilities at initialization; clients expose them via the
38
+ `capabilities` property.
39
+ """
40
+
41
+ # Device Operations
42
+ device_firmware_update: bool = False
43
+ firmware_update_trigger: bool = False
44
+ firmware_updates: bool = False
45
+ linking: bool = False
46
+ value_usage_reporting: bool = False
47
+
48
+ # Metadata Operations
49
+ functions: bool = False
50
+ rooms: bool = False
51
+ metadata: bool = False
52
+ rename: bool = False
53
+ rega_id_lookup: bool = False
54
+ service_messages: bool = False
55
+ system_update_info: bool = False
56
+ inbox_devices: bool = False
57
+ install_mode: bool = False
58
+
59
+ # Programs & System Variables
60
+ programs: bool = False
61
+
62
+ # Backup
63
+ backup: bool = False
64
+
65
+ # Connection
66
+ ping_pong: bool = False
67
+ push_updates: bool = True
68
+ rpc_callback: bool = True
69
+
70
+
71
+ # Default capability sets for each backend type.
72
+ # These can be adjusted at runtime based on interface type or system info.
73
+
74
+ CCU_CAPABILITIES: Final = BackendCapabilities(
75
+ device_firmware_update=True,
76
+ firmware_update_trigger=True,
77
+ firmware_updates=True,
78
+ linking=True,
79
+ value_usage_reporting=True,
80
+ functions=True,
81
+ rooms=True,
82
+ metadata=True,
83
+ rename=True,
84
+ rega_id_lookup=True,
85
+ service_messages=True,
86
+ system_update_info=True,
87
+ inbox_devices=True,
88
+ install_mode=True,
89
+ programs=True,
90
+ backup=True,
91
+ ping_pong=True,
92
+ push_updates=True,
93
+ rpc_callback=True,
94
+ )
95
+
96
+ JSON_CCU_CAPABILITIES: Final = BackendCapabilities(
97
+ device_firmware_update=False,
98
+ firmware_update_trigger=False,
99
+ firmware_updates=False,
100
+ linking=False,
101
+ value_usage_reporting=False,
102
+ functions=False,
103
+ rooms=False,
104
+ metadata=False,
105
+ rename=False,
106
+ rega_id_lookup=False,
107
+ service_messages=False,
108
+ system_update_info=False,
109
+ inbox_devices=False,
110
+ install_mode=False,
111
+ programs=False,
112
+ backup=False,
113
+ ping_pong=False,
114
+ push_updates=True,
115
+ rpc_callback=False,
116
+ )
117
+
118
+ HOMEGEAR_CAPABILITIES: Final = BackendCapabilities(
119
+ device_firmware_update=False,
120
+ firmware_update_trigger=False,
121
+ firmware_updates=False,
122
+ linking=False,
123
+ value_usage_reporting=False,
124
+ functions=False,
125
+ rooms=False,
126
+ metadata=False,
127
+ rename=False,
128
+ rega_id_lookup=False,
129
+ service_messages=False,
130
+ system_update_info=False,
131
+ inbox_devices=False,
132
+ install_mode=False,
133
+ programs=False,
134
+ backup=False,
135
+ ping_pong=False,
136
+ push_updates=True,
137
+ rpc_callback=True,
138
+ )