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
aiohomematic/const.py ADDED
@@ -0,0 +1,2207 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Constants used by aiohomematic.
5
+
6
+ Public API of this module is defined by __all__.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Iterable, Mapping
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime
14
+ from enum import Enum, IntEnum, StrEnum
15
+ import inspect
16
+ import os
17
+ import re
18
+ import sys
19
+ from types import MappingProxyType
20
+ from typing import Any, Final, NamedTuple, Required, TypedDict
21
+
22
+ VERSION: Final = "2026.1.29"
23
+
24
+ # Detect test speedup mode via environment
25
+ _TEST_SPEEDUP: Final = (
26
+ bool(os.getenv("AIOHOMEMATIC_TEST_SPEEDUP")) or ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules)
27
+ )
28
+
29
+ # default
30
+ DEFAULT_DELAY_NEW_DEVICE_CREATION: Final = False
31
+ DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK: Final = False
32
+ DEFAULT_ENABLE_PROGRAM_SCAN: Final = True
33
+ DEFAULT_ENABLE_SYSVAR_SCAN: Final = True
34
+ DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS: Final[frozenset[str]] = frozenset()
35
+ DEFAULT_INCLUDE_INTERNAL_PROGRAMS: Final = False
36
+ DEFAULT_INCLUDE_INTERNAL_SYSVARS: Final = True
37
+ DEFAULT_LOCALE: Final = "en"
38
+ DEFAULT_MAX_READ_WORKERS: Final = 1
39
+ DEFAULT_MAX_WORKERS: Final = 1
40
+ DEFAULT_MULTIPLIER: Final = 1.0
41
+ DEFAULT_OPTIONAL_SETTINGS: Final[tuple[OptionalSettings | str, ...]] = ()
42
+ DEFAULT_PROGRAM_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
43
+ DEFAULT_SESSION_RECORDER_START_FOR_SECONDS: Final = 180
44
+ DEFAULT_STORAGE_DIRECTORY: Final = "aiohomematic_storage"
45
+ DEFAULT_SYSVAR_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
46
+ DEFAULT_TLS: Final = False
47
+ DEFAULT_UN_IGNORES: Final[frozenset[str]] = frozenset()
48
+ DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE: Final = True
49
+ DEFAULT_VERIFY_TLS: Final = False
50
+ DEFAULT_INCLUDE_DEFAULT_DPS: Final = True
51
+
52
+
53
+ class TimeoutConfig(NamedTuple):
54
+ """
55
+ Configuration for various timeout and interval settings.
56
+
57
+ All values are in seconds unless otherwise noted.
58
+ """
59
+
60
+ reconnect_initial_delay: float = 0.5 if _TEST_SPEEDUP else 2
61
+ """Initial delay before first reconnect attempt (default: 2s)."""
62
+
63
+ reconnect_max_delay: float = 1 if _TEST_SPEEDUP else 120
64
+ """Maximum delay between reconnect attempts after exponential backoff (default: 120s)."""
65
+
66
+ reconnect_backoff_factor: float = 2
67
+ """Multiplier for exponential backoff on reconnect attempts (default: 2)."""
68
+
69
+ reconnect_initial_cooldown: float = 0.5 if _TEST_SPEEDUP else 30
70
+ """Initial cool-down period after connection loss before starting TCP checks (default: 30s)."""
71
+
72
+ reconnect_tcp_check_timeout: float = 1 if _TEST_SPEEDUP else 60
73
+ """Maximum time to wait for TCP port to become available before giving up (default: 60s)."""
74
+
75
+ reconnect_tcp_check_interval: float = 0.5 if _TEST_SPEEDUP else 5
76
+ """Interval between TCP port checks during reconnection (default: 5s)."""
77
+
78
+ reconnect_warmup_delay: float = 0.5 if _TEST_SPEEDUP else 15
79
+ """
80
+ Warmup delay after first successful RPC check before attempting init (default: 15s).
81
+
82
+ After TCP port becomes available and first listMethods succeeds, this delay allows
83
+ CCU services to fully stabilize. A second listMethods call verifies stability before
84
+ init() is attempted.
85
+ """
86
+
87
+ callback_warn_interval: float = (1 if _TEST_SPEEDUP else 15) * 12
88
+ """Interval before warning about missing callback events (default: 180s = 3min)."""
89
+
90
+ rpc_timeout: float = 5 if _TEST_SPEEDUP else 60
91
+ """Default timeout for RPC calls (default: 60s)."""
92
+
93
+ ping_timeout: float = 2 if _TEST_SPEEDUP else 10
94
+ """Timeout for ping/connectivity check operations (default: 10s)."""
95
+
96
+ connectivity_error_threshold: int = 1
97
+ """Number of consecutive connectivity failures before marking devices unavailable (default: 1)."""
98
+
99
+
100
+ DEFAULT_TIMEOUT_CONFIG: Final = TimeoutConfig()
101
+
102
+
103
+ class ScheduleTimerConfig(NamedTuple):
104
+ """
105
+ Configuration for scheduler intervals and timeouts.
106
+
107
+ All values are in seconds.
108
+ """
109
+
110
+ connection_checker_interval: int = 1 if _TEST_SPEEDUP else 15
111
+ """Interval between connection health checks (default: 15s)."""
112
+
113
+ device_firmware_check_interval: int = 21600 # 6h
114
+ """Interval for periodic device firmware update checks (default: 6h)."""
115
+
116
+ device_firmware_delivering_check_interval: int = 3600 # 1h
117
+ """Interval for checking firmware delivery progress (default: 1h)."""
118
+
119
+ device_firmware_updating_check_interval: int = 300 # 5m
120
+ """Interval for checking firmware update progress (default: 5m)."""
121
+
122
+ master_poll_after_send_intervals: tuple[int, ...] = (5,)
123
+ """Interval for polling HM master after sending commands (default: 5s)."""
124
+
125
+ metrics_refresh_interval: int = 60
126
+ """Interval for refreshing metrics hub sensors (default: 60s)."""
127
+
128
+ periodic_refresh_interval: int = 15
129
+ """Interval for periodic data refresh (default: 15s)."""
130
+
131
+ sys_scan_interval: int = 30
132
+ """Interval for system variable and program scans (default: 30s)."""
133
+
134
+ system_update_check_interval: int = 14400 # 4h
135
+ """Interval for periodic system update checks (default: 4h)."""
136
+
137
+ system_update_progress_check_interval: int = 30 # 30s
138
+ """Interval for checking system update progress during active update (default: 30s)."""
139
+
140
+ system_update_progress_timeout: int = 1800 # 30min
141
+ """Timeout for system update monitoring (default: 30min)."""
142
+
143
+
144
+ DEFAULT_SCHEDULE_TIMER_CONFIG: Final = ScheduleTimerConfig()
145
+
146
+ # Default encoding for json service calls, persistent cache
147
+ UTF_8: Final = "utf-8"
148
+ # Default encoding for xmlrpc service calls and script files
149
+ ISO_8859_1: Final = "iso-8859-1"
150
+
151
+ BIDCOS_DEVICE_CHANNEL_DUMMY: Final = 999
152
+
153
+ # Password can be empty.
154
+ # Allowed characters: A-Z, a-z, 0-9, .!$():;#-
155
+ # The CCU WebUI also supports ÄäÖöÜüß, but these characters are not supported by the XmlRPC servers
156
+ CCU_PASSWORD_PATTERN: Final = re.compile(r"[A-Za-z0-9.!$():;#-]{0,}")
157
+ # Pattern is bigger than needed
158
+ CHANNEL_ADDRESS_PATTERN: Final = re.compile(r"^[0-9a-zA-Z-]{5,20}:[0-9]{1,3}$")
159
+ DEVICE_ADDRESS_PATTERN: Final = re.compile(r"^[0-9a-zA-Z-]{5,20}$")
160
+
161
+ HOSTNAME_PATTERN: Final = re.compile(
162
+ r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*$"
163
+ )
164
+ IPV4_PATTERN: Final = re.compile(
165
+ r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
166
+ )
167
+ IPV6_PATTERN: Final = re.compile(r"^\[?[0-9a-fA-F:]+\]?$")
168
+
169
+ HTMLTAG_PATTERN: Final = re.compile(r"<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});")
170
+ SCHEDULER_PROFILE_PATTERN: Final = re.compile(
171
+ r"^P[1-6]_(ENDTIME|TEMPERATURE)_(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)_([1-9]|1[0-3])$"
172
+ )
173
+ SCHEDULER_TIME_PATTERN: Final = re.compile(r"^(([0-1]{0,1}[0-9])|(2[0-4])):[0-5][0-9]")
174
+ WEEK_PROFILE_PATTERN: Final = re.compile(r".*WEEK_PROFILE$")
175
+
176
+ ALWAYS_ENABLE_SYSVARS_BY_ID: Final[frozenset[str]] = frozenset({"40", "41"})
177
+ RENAME_SYSVAR_BY_NAME: Final[Mapping[str, str]] = MappingProxyType(
178
+ {
179
+ "${sysVarAlarmMessages}": "ALARM_MESSAGES",
180
+ "${sysVarPresence}": "PRESENCE",
181
+ "${sysVarServiceMessages}": "SERVICE_MESSAGES",
182
+ }
183
+ )
184
+
185
+ ADDRESS_SEPARATOR: Final = ":"
186
+ BLOCK_LOG_TIMEOUT: Final = 60
187
+ CONTENT_PATH: Final = "cache"
188
+ CONF_PASSWORD: Final = "password"
189
+ CONF_USERNAME: Final = "username"
190
+
191
+ DATETIME_FORMAT: Final = "%d.%m.%Y %H:%M:%S"
192
+ DATETIME_FORMAT_MILLIS: Final = "%d.%m.%Y %H:%M:%S.%f'"
193
+ DUMMY_SERIAL: Final = "SN0815"
194
+ FILE_DEVICES: Final = "homematic_devices"
195
+ FILE_INCIDENTS: Final = "homematic_incidents"
196
+ FILE_PARAMSETS: Final = "homematic_paramsets"
197
+ FILE_SESSION_RECORDER: Final = "homematic_session_recorder"
198
+ FILE_NAME_TS_PATTERN: Final = "%Y%m%d_%H%M%S"
199
+ INCIDENT_STORE_MAX_PER_TYPE: Final = 50
200
+ SUB_DIRECTORY_CACHE: Final = "cache"
201
+ SUB_DIRECTORY_SESSION: Final = "session"
202
+ HUB_PATH: Final = "hub"
203
+ IDENTIFIER_SEPARATOR: Final = "@"
204
+ INIT_DATETIME: Final = datetime.strptime("01.01.1970 00:00:00", DATETIME_FORMAT)
205
+ IP_ANY_V4: Final = "0.0.0.0"
206
+ JSON_SESSION_AGE: Final = 90
207
+
208
+ # Login rate limiting constants
209
+ LOGIN_MAX_FAILED_ATTEMPTS: Final = 10
210
+ LOGIN_INITIAL_BACKOFF_SECONDS: Final = 1.0
211
+ LOGIN_MAX_BACKOFF_SECONDS: Final = 60.0
212
+ LOGIN_BACKOFF_MULTIPLIER: Final = 2.0
213
+
214
+ KWARGS_ARG_CUSTOM_ID: Final = "custom_id"
215
+ KWARGS_ARG_DATA_POINT: Final = "data_point"
216
+ LAST_COMMAND_SEND_TRACKER_CLEANUP_THRESHOLD: Final = 100 # Cleanup when tracker size exceeds this
217
+ LAST_COMMAND_SEND_STORE_TIMEOUT: Final = 60
218
+
219
+ # Resource limits for internal collections
220
+ COMMAND_TRACKER_MAX_SIZE: Final = 500 # Maximum entries in command tracker
221
+ COMMAND_TRACKER_WARNING_THRESHOLD: Final = 400 # Log warning when approaching limit
222
+ PING_PONG_CACHE_MAX_SIZE: Final = 100 # Maximum entries in ping/pong cache per interface
223
+ LOCAL_HOST: Final = "127.0.0.1"
224
+ MAX_CACHE_AGE: Final = 10
225
+ MAX_CONCURRENT_HTTP_SESSIONS: Final = 3
226
+ MAX_WAIT_FOR_CALLBACK: Final = 60
227
+ NO_CACHE_ENTRY: Final = "NO_CACHE_ENTRY"
228
+ DEVICE_DESCRIPTIONS_ZIP_DIR: Final = "device_descriptions"
229
+ PARAMSET_DESCRIPTIONS_ZIP_DIR: Final = "paramset_descriptions"
230
+ PATH_JSON_RPC: Final = "/api/homematic.cgi"
231
+ PING_PONG_MISMATCH_COUNT: Final = 15
232
+ PING_PONG_MISMATCH_COUNT_TTL: Final = 300
233
+ PORT_ANY: Final = 0
234
+
235
+ # Backend detection ports
236
+ # Format: (non-TLS port, TLS port)
237
+ DETECTION_PORT_BIDCOS_RF: Final = (2001, 42001)
238
+ DETECTION_PORT_HMIP_RF: Final = (2010, 42010)
239
+ DETECTION_PORT_BIDCOS_WIRED: Final = (2000, 42000)
240
+ DETECTION_PORT_VIRTUAL_DEVICES: Final = (9292, 49292)
241
+ DETECTION_PORT_JSON_RPC: Final = ((80, False), (443, True)) # (port, tls)
242
+
243
+ # Default JSON-RPC ports
244
+ DEFAULT_JSON_RPC_PORT: Final = 80
245
+ DEFAULT_JSON_RPC_TLS_PORT: Final = 443
246
+
247
+ HUB_ADDRESS: Final = "hub"
248
+ INSTALL_MODE_ADDRESS: Final = "install_mode"
249
+ PROGRAM_ADDRESS: Final = "program"
250
+ REGA_SCRIPT_PATH: Final = "../rega_scripts"
251
+ REPORT_VALUE_USAGE_DATA: Final = "reportValueUsageData"
252
+ REPORT_VALUE_USAGE_VALUE_ID: Final = "PRESS_SHORT"
253
+ SYSVAR_ADDRESS: Final = "sysvar"
254
+ TIMEOUT: Final = 5 if _TEST_SPEEDUP else 60 # default timeout for a connection
255
+ UN_IGNORE_WILDCARD: Final = "all"
256
+ WAIT_FOR_CALLBACK: Final[int | None] = None
257
+
258
+ # Scheduler sleep durations (used by central scheduler loop)
259
+ SCHEDULER_NOT_STARTED_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 10
260
+ SCHEDULER_LOOP_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 5
261
+
262
+ # Path
263
+ HUB_SET_PATH_ROOT: Final = "hub/set"
264
+ HUB_STATE_PATH_ROOT: Final = "hub/status"
265
+ PROGRAM_SET_PATH_ROOT: Final = "program/set"
266
+ PROGRAM_STATE_PATH_ROOT: Final = "program/status"
267
+ SET_PATH_ROOT: Final = "device/set"
268
+ STATE_PATH_ROOT: Final = "device/status"
269
+ SYSVAR_SET_PATH_ROOT: Final = "sysvar/set"
270
+ SYSVAR_STATE_PATH_ROOT: Final = "sysvar/status"
271
+ VIRTDEV_SET_PATH_ROOT: Final = "virtdev/set"
272
+ VIRTDEV_STATE_PATH_ROOT: Final = "virtdev/status"
273
+
274
+ # Metric sensor names
275
+ METRICS_SENSOR_SYSTEM_HEALTH_NAME: Final = "system_health"
276
+ METRICS_SENSOR_CONNECTION_LATENCY_NAME: Final = "connection_latency"
277
+ METRICS_SENSOR_LAST_EVENT_AGE_NAME: Final = "last_event_age"
278
+
279
+ CONNECTIVITY_SENSOR_PREFIX: Final = "Connectivity"
280
+ INBOX_SENSOR_NAME: Final = "inbox"
281
+
282
+
283
+ class Backend(StrEnum):
284
+ """Enum with supported aiohomematic backends."""
285
+
286
+ CCU = "CCU"
287
+ HOMEGEAR = "Homegear"
288
+ PYDEVCCU = "PyDevCCU"
289
+
290
+
291
+ class CCUType(StrEnum):
292
+ """
293
+ Enum with CCU types.
294
+
295
+ CCU: Original CCU2/CCU3 hardware and debmatic (CCU clone).
296
+ OPENCCU: OpenCCU - modern variants with online update check.
297
+ """
298
+
299
+ CCU = "CCU"
300
+ OPENCCU = "OpenCCU"
301
+ UNKNOWN = "Unknown"
302
+
303
+
304
+ class SystemEventType(StrEnum):
305
+ """Enum with aiohomematic system events."""
306
+
307
+ DELETE_DEVICES = "deleteDevices"
308
+ DEVICES_CREATED = "devicesCreated"
309
+ DEVICES_DELAYED = "devicesDelayed"
310
+ ERROR = "error"
311
+ HUB_REFRESHED = "hubDataPointRefreshed"
312
+ LIST_DEVICES = "listDevices"
313
+ NEW_DEVICES = "newDevices"
314
+ REPLACE_DEVICE = "replaceDevice"
315
+ RE_ADDED_DEVICE = "readdedDevice"
316
+ UPDATE_DEVICE = "updateDevice"
317
+
318
+
319
+ class CallSource(StrEnum):
320
+ """Enum with sources for calls."""
321
+
322
+ HA_INIT = "ha_init"
323
+ HM_INIT = "hm_init"
324
+ MANUAL_OR_SCHEDULED = "manual_or_scheduled"
325
+
326
+
327
+ class ServiceScope(StrEnum):
328
+ """
329
+ Enum defining the scope of service methods.
330
+
331
+ Used by @inspector and @bind_collector decorators to control whether
332
+ a method is exposed as a service method (lib_service attribute).
333
+
334
+ Values:
335
+ EXTERNAL: Methods intended for external consumers (e.g., Home Assistant).
336
+ These are user-invokable commands like turn_on, turn_off, set_temperature.
337
+ Methods with this scope appear in service_method_names.
338
+ INTERNAL: Infrastructure methods for library operation.
339
+ These are internal methods like load_data_point_value, fetch_*_data.
340
+ Methods with this scope do NOT appear in service_method_names.
341
+ """
342
+
343
+ EXTERNAL = "external"
344
+ INTERNAL = "internal"
345
+
346
+
347
+ class CalculatedParameter(StrEnum):
348
+ """Enum with calculated Homematic parameters."""
349
+
350
+ APPARENT_TEMPERATURE = "APPARENT_TEMPERATURE"
351
+ DEW_POINT = "DEW_POINT"
352
+ DEW_POINT_SPREAD = "DEW_POINT_SPREAD"
353
+ ENTHALPY = "ENTHALPY"
354
+ FROST_POINT = "FROST_POINT"
355
+ OPERATING_VOLTAGE_LEVEL = "OPERATING_VOLTAGE_LEVEL"
356
+ VAPOR_CONCENTRATION = "VAPOR_CONCENTRATION"
357
+
358
+
359
+ class ProfileKey(StrEnum):
360
+ """Enum for custom data point definitions."""
361
+
362
+ ADDITIONAL_DPS = "additional_dps"
363
+ ALLOW_UNDEFINED_GENERIC_DPS = "allow_undefined_generic_dps"
364
+ DEFAULT_DPS = "default_dps"
365
+ DEVICE_DEFINITIONS = "device_definitions"
366
+ DEVICE_GROUP = "device_group"
367
+ FIELDS = "fields"
368
+ INCLUDE_DEFAULT_DPS = "include_default_dps"
369
+ PRIMARY_CHANNEL = "primary_channel"
370
+ REPEATABLE_FIELDS = "repeatable_fields"
371
+ SECONDARY_CHANNELS = "secondary_channels"
372
+ STATE_CHANNEL = "state_channel"
373
+ VISIBLE_FIELDS = "visible_fields"
374
+ VISIBLE_REPEATABLE_FIELDS = "visible_repeatable_fields"
375
+
376
+
377
+ class ChannelOffset(IntEnum):
378
+ """
379
+ Semantic channel offsets relative to the primary channel.
380
+
381
+ Used in profile definitions to reference channels by their semantic role
382
+ rather than magic numbers.
383
+ """
384
+
385
+ STATE = -1
386
+ """State channel offset (e.g., ACTIVITY_STATE for covers)."""
387
+
388
+ SENSOR = -2
389
+ """Sensor channel offset (e.g., WATER_FLOW for irrigation)."""
390
+
391
+ CONFIG = -5
392
+ """Configuration channel offset (e.g., for WGTC thermostat)."""
393
+
394
+
395
+ class CacheInvalidationReason(StrEnum):
396
+ """Reason for cache invalidation."""
397
+
398
+ DEVICE_ADDED = "device_added"
399
+ """Cache invalidated due to device being added."""
400
+
401
+ DEVICE_REMOVED = "device_removed"
402
+ """Cache invalidated due to device being removed."""
403
+
404
+ DEVICE_UPDATED = "device_updated"
405
+ """Cache invalidated due to device being updated."""
406
+
407
+ REFRESH = "refresh"
408
+ """Cache invalidated due to scheduled refresh."""
409
+
410
+ MANUAL = "manual"
411
+ """Cache invalidated manually."""
412
+
413
+ STARTUP = "startup"
414
+ """Cache invalidated during startup."""
415
+
416
+ SHUTDOWN = "shutdown"
417
+ """Cache invalidated during shutdown."""
418
+
419
+
420
+ class CacheType(StrEnum):
421
+ """Cache type identifiers."""
422
+
423
+ DEVICE_DESCRIPTION = "device_description"
424
+ """Device description cache."""
425
+
426
+ PARAMSET_DESCRIPTION = "paramset_description"
427
+ """Paramset description cache."""
428
+
429
+ DATA = "data"
430
+ """Data cache."""
431
+
432
+ DETAILS = "details"
433
+ """Device details cache."""
434
+
435
+ VISIBILITY = "visibility"
436
+ """Parameter visibility cache."""
437
+
438
+
439
+ class CentralState(StrEnum):
440
+ """
441
+ Central State Machine states for overall system health orchestration.
442
+
443
+ This enum defines the states for the Central State Machine which
444
+ orchestrates the overall system state based on individual client states.
445
+
446
+ State Machine
447
+ -------------
448
+ ```
449
+ STARTING ──► INITIALIZING ──► RUNNING ◄──► DEGRADED
450
+ │ │ │
451
+ │ ▼ ▼
452
+ │ RECOVERING ◄────┘
453
+ │ │
454
+ │ ├──► RUNNING (all recovered)
455
+ │ ├──► DEGRADED (partial recovery)
456
+ │ └──► FAILED (max retries)
457
+
458
+ └──► FAILED (critical init error)
459
+
460
+ STOPPED ◄── (from any state via stop())
461
+ ```
462
+
463
+ Valid Transitions
464
+ -----------------
465
+ - STARTING → INITIALIZING, STOPPED
466
+ - INITIALIZING → RUNNING, DEGRADED, FAILED, STOPPED
467
+ - RUNNING → DEGRADED, RECOVERING, STOPPED
468
+ - DEGRADED → RUNNING, RECOVERING, FAILED, STOPPED
469
+ - RECOVERING → RUNNING, DEGRADED, FAILED, STOPPED
470
+ - FAILED → RECOVERING, STOPPED
471
+ - STOPPED → (terminal)
472
+ """
473
+
474
+ STARTING = "starting"
475
+ """Central is being created."""
476
+
477
+ INITIALIZING = "initializing"
478
+ """Clients are being initialized."""
479
+
480
+ RUNNING = "running"
481
+ """Normal operation - all clients connected."""
482
+
483
+ DEGRADED = "degraded"
484
+ """Limited operation - at least one client not connected."""
485
+
486
+ RECOVERING = "recovering"
487
+ """Active recovery in progress."""
488
+
489
+ FAILED = "failed"
490
+ """Critical error - manual intervention required."""
491
+
492
+ STOPPED = "stopped"
493
+ """Central has been stopped."""
494
+
495
+
496
+ class FailureReason(StrEnum):
497
+ """
498
+ Reason for a failure state in state machines.
499
+
500
+ This enum provides detailed failure categorization for FAILED states
501
+ in both ClientStateMachine and CentralStateMachine, enabling integrations
502
+ to distinguish between different error types and show appropriate messages.
503
+
504
+ Usage
505
+ -----
506
+ When transitioning to a FAILED state, specify the failure reason:
507
+
508
+ ```python
509
+ state_machine.transition_to(
510
+ target=ClientState.FAILED,
511
+ reason="Authentication failed",
512
+ failure_reason=FailureReason.AUTH,
513
+ )
514
+ ```
515
+
516
+ The integration can then check the failure reason:
517
+
518
+ ```python
519
+ if central.state_machine.failure_reason == FailureReason.AUTH:
520
+ show_auth_error_dialog()
521
+ elif central.state_machine.failure_reason == FailureReason.NETWORK:
522
+ show_connection_error_dialog()
523
+ ```
524
+ """
525
+
526
+ NONE = "none"
527
+ """No failure - normal operation."""
528
+
529
+ AUTH = "auth"
530
+ """Authentication/authorization failure (wrong credentials)."""
531
+
532
+ NETWORK = "network"
533
+ """Network connectivity issue (host unreachable, connection refused)."""
534
+
535
+ INTERNAL = "internal"
536
+ """Internal backend error (CCU internal error)."""
537
+
538
+ TIMEOUT = "timeout"
539
+ """Operation timed out."""
540
+
541
+ CIRCUIT_BREAKER = "circuit_breaker"
542
+ """Circuit breaker is open due to repeated failures."""
543
+
544
+ UNKNOWN = "unknown"
545
+ """Unknown or unclassified error."""
546
+
547
+
548
+ class UpdateDeviceHint(IntEnum):
549
+ """
550
+ Hint values for updateDevice callback from Homematic backend.
551
+
552
+ The CCU sends these hint values to indicate the type of device update:
553
+ - FIRMWARE: Device firmware was updated, requires cache invalidation
554
+ - LINKS: Only link partners changed, no cache invalidation needed
555
+ """
556
+
557
+ FIRMWARE = 0
558
+ """Device firmware was updated - requires cache invalidation and reload."""
559
+
560
+ LINKS = 1
561
+ """Link partners changed - no cache invalidation needed."""
562
+
563
+
564
+ class ConnectionStage(IntEnum):
565
+ """
566
+ Reconnection stage progression.
567
+
568
+ Stages during reconnection after connection loss:
569
+ - LOST: Connection was lost, initiating reconnection
570
+ - TCP_AVAILABLE: TCP port is responding
571
+ - RPC_AVAILABLE: RPC service is responding (listMethods)
572
+ - WARMUP: Waiting for services to stabilize
573
+ - ESTABLISHED: Connection fully established
574
+ """
575
+
576
+ LOST = 0
577
+ """Connection lost, initiating reconnection."""
578
+
579
+ TCP_AVAILABLE = 1
580
+ """TCP port is responding."""
581
+
582
+ RPC_AVAILABLE = 2
583
+ """RPC service is responding (listMethods passed)."""
584
+
585
+ WARMUP = 3
586
+ """Warmup period - waiting for services to stabilize."""
587
+
588
+ ESTABLISHED = 4
589
+ """Connection fully re-established."""
590
+
591
+ @property
592
+ def display_name(self) -> str:
593
+ """Return human-readable stage name."""
594
+ names: dict[int, str] = {
595
+ 0: "Connection Lost",
596
+ 1: "TCP Port Available",
597
+ 2: "RPC Responding",
598
+ 3: "Warmup Period",
599
+ 4: "Connection Established",
600
+ }
601
+ return names.get(self.value, "Unknown")
602
+
603
+
604
+ class RecoveryStage(StrEnum):
605
+ """
606
+ Stages of the unified connection recovery process.
607
+
608
+ The ConnectionRecoveryCoordinator progresses through these stages
609
+ when recovering a failed connection. Each stage emits a
610
+ RecoveryStageChangedEvent for observability.
611
+
612
+ Stage Progression
613
+ -----------------
614
+ Normal recovery: IDLE → DETECTING → COOLDOWN → TCP_CHECKING → RPC_CHECKING
615
+ → WARMING_UP → STABILITY_CHECK → RECONNECTING → DATA_LOADING
616
+ → RECOVERED
617
+
618
+ Failed recovery: Any stage → FAILED → HEARTBEAT (periodic retry)
619
+
620
+ Retry from FAILED: HEARTBEAT → TCP_CHECKING → ... → RECOVERED (or back to FAILED)
621
+ """
622
+
623
+ IDLE = "idle"
624
+ """No recovery in progress."""
625
+
626
+ DETECTING = "detecting"
627
+ """Connection loss detected, preparing recovery."""
628
+
629
+ COOLDOWN = "cooldown"
630
+ """Initial cool-down period before recovery attempt."""
631
+
632
+ TCP_CHECKING = "tcp_checking"
633
+ """Checking TCP port availability (non-invasive)."""
634
+
635
+ RPC_CHECKING = "rpc_checking"
636
+ """Checking RPC service responds (listMethods)."""
637
+
638
+ WARMING_UP = "warming_up"
639
+ """Waiting for services to stabilize after RPC responds."""
640
+
641
+ STABILITY_CHECK = "stability_check"
642
+ """Confirming RPC stability before reconnection."""
643
+
644
+ RECONNECTING = "reconnecting"
645
+ """Performing full client reconnection (init call)."""
646
+
647
+ DATA_LOADING = "data_loading"
648
+ """Loading device and paramset data post-reconnect."""
649
+
650
+ RECOVERED = "recovered"
651
+ """Recovery completed successfully."""
652
+
653
+ FAILED = "failed"
654
+ """Recovery failed after max retries."""
655
+
656
+ HEARTBEAT = "heartbeat"
657
+ """Periodic retry attempt in FAILED state."""
658
+
659
+ @property
660
+ def display_name(self) -> str:
661
+ """Return human-readable stage name."""
662
+ names: dict[str, str] = {
663
+ "idle": "Idle",
664
+ "detecting": "Detecting Loss",
665
+ "cooldown": "Cool-down",
666
+ "tcp_checking": "TCP Check",
667
+ "rpc_checking": "RPC Check",
668
+ "warming_up": "Warming Up",
669
+ "stability_check": "Stability Check",
670
+ "reconnecting": "Reconnecting",
671
+ "data_loading": "Loading Data",
672
+ "recovered": "Recovered",
673
+ "failed": "Failed",
674
+ "heartbeat": "Heartbeat Retry",
675
+ }
676
+ return names.get(self.value, "Unknown")
677
+
678
+
679
+ class RecoveryResult(StrEnum):
680
+ """Result of a recovery attempt."""
681
+
682
+ SUCCESS = "success"
683
+ """Recovery was fully successful."""
684
+
685
+ PARTIAL = "partial"
686
+ """Some clients recovered, others still failed."""
687
+
688
+ FAILED = "failed"
689
+ """Recovery failed for all clients."""
690
+
691
+ MAX_RETRIES = "max_retries"
692
+ """Maximum retry attempts reached."""
693
+
694
+ CANCELLED = "cancelled"
695
+ """Recovery was cancelled (e.g., during shutdown)."""
696
+
697
+
698
+ class CommandRxMode(StrEnum):
699
+ """Enum for Homematic rx modes for commands."""
700
+
701
+ BURST = "BURST"
702
+ WAKEUP = "WAKEUP"
703
+
704
+
705
+ class DataOperationResult(Enum):
706
+ """Enum with data operation results."""
707
+
708
+ LOAD_FAIL = 0
709
+ LOAD_SUCCESS = 1
710
+ SAVE_FAIL = 10
711
+ SAVE_SUCCESS = 11
712
+ NO_LOAD = 20
713
+ NO_SAVE = 21
714
+
715
+
716
+ class DataPointCategory(StrEnum):
717
+ """Enum with data point types."""
718
+
719
+ ACTION = "action"
720
+ ACTION_SELECT = "action_select"
721
+ BINARY_SENSOR = "binary_sensor"
722
+ BUTTON = "button"
723
+ CLIMATE = "climate"
724
+ COVER = "cover"
725
+ EVENT = "event"
726
+ HUB_BINARY_SENSOR = "hub_binary_sensor"
727
+ HUB_BUTTON = "hub_button"
728
+ HUB_NUMBER = "hub_number"
729
+ HUB_SELECT = "hub_select"
730
+ HUB_SENSOR = "hub_sensor"
731
+ HUB_SWITCH = "hub_switch"
732
+ HUB_TEXT = "hub_text"
733
+ HUB_UPDATE = "hub_update"
734
+ LIGHT = "light"
735
+ LOCK = "lock"
736
+ NUMBER = "number"
737
+ SELECT = "select"
738
+ SENSOR = "sensor"
739
+ SIREN = "siren"
740
+ SWITCH = "switch"
741
+ TEXT = "text"
742
+ TEXT_DISPLAY = "text_display"
743
+ UNDEFINED = "undefined"
744
+ UPDATE = "update"
745
+ VALVE = "valve"
746
+
747
+
748
+ class DataPointKey(NamedTuple):
749
+ """Key for data points."""
750
+
751
+ interface_id: str
752
+ channel_address: str
753
+ paramset_key: ParamsetKey
754
+ parameter: str
755
+
756
+
757
+ class DataPointUsage(StrEnum):
758
+ """Enum with usage information."""
759
+
760
+ CDP_PRIMARY = "ce_primary"
761
+ CDP_SECONDARY = "ce_secondary"
762
+ CDP_VISIBLE = "ce_visible"
763
+ DATA_POINT = "data_point"
764
+ EVENT = "event"
765
+ NO_CREATE = "no_create"
766
+
767
+
768
+ class ParameterStatus(StrEnum):
769
+ """
770
+ Status values for paired *_STATUS parameters.
771
+
772
+ These indicate the validity/quality of the associated parameter value.
773
+ HmIP devices use string-based ENUMs for status parameters.
774
+ Note: Some *_STATUS parameters (LED_STATUS, BACKLIGHT_AT_STATUS, etc.)
775
+ have different value semantics and should not use this enum.
776
+ """
777
+
778
+ NORMAL = "NORMAL"
779
+ """Value is valid and within expected range."""
780
+
781
+ UNKNOWN = "UNKNOWN"
782
+ """Value is unknown (device hasn't reported yet, or communication issue)."""
783
+
784
+ OVERFLOW = "OVERFLOW"
785
+ """Value exceeds the maximum expected range."""
786
+
787
+ UNDERFLOW = "UNDERFLOW"
788
+ """Value is below the minimum expected range."""
789
+
790
+ ERROR = "ERROR"
791
+ """Measurement error occurred."""
792
+
793
+ INVALID = "INVALID"
794
+ """Value is invalid."""
795
+
796
+ UNUSED = "UNUSED"
797
+ """Parameter is not used."""
798
+
799
+ EXTERNAL = "EXTERNAL"
800
+ """Value is from an external source."""
801
+
802
+
803
+ class DescriptionMarker(StrEnum):
804
+ """Enum with default description markers."""
805
+
806
+ HAHM = "HAHM"
807
+ HX = "HX"
808
+ INTERNAL = "INTERNAL"
809
+ MQTT = "MQTT"
810
+
811
+
812
+ class DeviceFirmwareState(StrEnum):
813
+ """Enum with Homematic device firmware states."""
814
+
815
+ UNKNOWN = "UNKNOWN"
816
+ UP_TO_DATE = "UP_TO_DATE"
817
+ LIVE_UP_TO_DATE = "LIVE_UP_TO_DATE"
818
+ NEW_FIRMWARE_AVAILABLE = "NEW_FIRMWARE_AVAILABLE"
819
+ LIVE_NEW_FIRMWARE_AVAILABLE = "LIVE_NEW_FIRMWARE_AVAILABLE"
820
+ DELIVER_FIRMWARE_IMAGE = "DELIVER_FIRMWARE_IMAGE"
821
+ LIVE_DELIVER_FIRMWARE_IMAGE = "LIVE_DELIVER_FIRMWARE_IMAGE"
822
+ READY_FOR_UPDATE = "READY_FOR_UPDATE"
823
+ DO_UPDATE_PENDING = "DO_UPDATE_PENDING"
824
+ PERFORMING_UPDATE = "PERFORMING_UPDATE"
825
+ BACKGROUND_UPDATE_NOT_SUPPORTED = "BACKGROUND_UPDATE_NOT_SUPPORTED"
826
+
827
+
828
+ class DeviceProfile(StrEnum):
829
+ """Enum for device profiles."""
830
+
831
+ IP_BUTTON_LOCK = "IPButtonLock"
832
+ IP_COVER = "IPCover"
833
+ IP_DIMMER = "IPDimmer"
834
+ IP_DRG_DALI = "IPDRGDALI"
835
+ IP_FIXED_COLOR_LIGHT = "IPFixedColorLight"
836
+ IP_GARAGE = "IPGarage"
837
+ IP_HDM = "IPHdm"
838
+ IP_IRRIGATION_VALVE = "IPIrrigationValve"
839
+ IP_LOCK = "IPLock"
840
+ IP_RGBW_LIGHT = "IPRGBW"
841
+ IP_SIMPLE_FIXED_COLOR_LIGHT = "IPSimpleFixedColorLight"
842
+ IP_SIMPLE_FIXED_COLOR_LIGHT_WIRED = "IPSimpleFixedColorLightWired"
843
+ IP_SIREN = "IPSiren"
844
+ IP_SIREN_SMOKE = "IPSirenSmoke"
845
+ IP_SOUND_PLAYER = "IPSoundPlayer"
846
+ IP_SOUND_PLAYER_LED = "IPSoundPlayerLed"
847
+ IP_SWITCH = "IPSwitch"
848
+ IP_TEXT_DISPLAY = "IPTextDisplay"
849
+ IP_THERMOSTAT = "IPThermostat"
850
+ IP_THERMOSTAT_GROUP = "IPThermostatGroup"
851
+ RF_BUTTON_LOCK = "RFButtonLock"
852
+ RF_COVER = "RfCover"
853
+ RF_DIMMER = "RfDimmer"
854
+ RF_DIMMER_COLOR = "RfDimmer_Color"
855
+ RF_DIMMER_COLOR_FIXED = "RfDimmer_Color_Fixed"
856
+ RF_DIMMER_COLOR_TEMP = "RfDimmer_Color_Temp"
857
+ RF_DIMMER_WITH_VIRT_CHANNEL = "RfDimmerWithVirtChannel"
858
+ RF_LOCK = "RfLock"
859
+ RF_SIREN = "RfSiren"
860
+ RF_SWITCH = "RfSwitch"
861
+ RF_THERMOSTAT = "RfThermostat"
862
+ RF_THERMOSTAT_GROUP = "RfThermostatGroup"
863
+ SIMPLE_RF_THERMOSTAT = "SimpleRfThermostat"
864
+
865
+
866
+ class DeviceTriggerEventType(StrEnum):
867
+ """Enum with aiohomematic event types."""
868
+
869
+ DEVICE_ERROR = "homematic.device_error"
870
+ IMPULSE = "homematic.impulse"
871
+ KEYPRESS = "homematic.keypress"
872
+
873
+
874
+ @dataclass(frozen=True, kw_only=True, slots=True)
875
+ class EventData:
876
+ """Data for device trigger events."""
877
+
878
+ interface_id: str
879
+ model: str
880
+ device_address: str
881
+ channel_no: int | None
882
+ parameter: str
883
+ value: Any = None
884
+
885
+
886
+ class Field(Enum):
887
+ """Enum for fields."""
888
+
889
+ ACOUSTIC_ALARM_ACTIVE = "acoustic_alarm_active"
890
+ ACOUSTIC_ALARM_SELECTION = "acoustic_alarm_selection"
891
+ ACOUSTIC_NOTIFICATION_SELECTION = "acoustic_notification_selection"
892
+ ACTIVE_PROFILE = "active_profile"
893
+ ACTIVITY_STATE = "activity_state"
894
+ AUTO_MODE = "auto_mode"
895
+ BOOST_MODE = "boost_mode"
896
+ BURST_LIMIT_WARNING = "burst_limit_warning"
897
+ BUTTON_LOCK = "button_lock"
898
+ CHANNEL_COLOR = "channel_color"
899
+ COLOR = "color"
900
+ COLOR_BEHAVIOUR = "color_behaviour"
901
+ COLOR_LEVEL = "color_temp"
902
+ COLOR_TEMPERATURE = "color_temperature"
903
+ COMBINED_PARAMETER = "combined_parameter"
904
+ COMFORT_MODE = "comfort_mode"
905
+ CONCENTRATION = "concentration"
906
+ CONTROL_MODE = "control_mode"
907
+ CURRENT = "current"
908
+ DEVICE_OPERATION_MODE = "device_operation_mode"
909
+ DIRECTION = "direction"
910
+ DISPLAY_DATA_ALIGNMENT = "display_data_alignment"
911
+ DISPLAY_DATA_BACKGROUND_COLOR = "display_data_background_color"
912
+ DISPLAY_DATA_ICON = "display_data_icon"
913
+ DISPLAY_DATA_TEXT_COLOR = "display_data_text_color"
914
+ DISPLAY_DATA_COMMIT = "display_data_commit"
915
+ DISPLAY_DATA_ID = "display_data_id"
916
+ DISPLAY_DATA_STRING = "display_data_string"
917
+ DOOR_COMMAND = "door_command"
918
+ DOOR_STATE = "door_state"
919
+ DURATION = "duration"
920
+ DURATION_UNIT = "duration_unit"
921
+ DURATION_VALUE = "duration_value"
922
+ DUTYCYCLE = "dutycycle"
923
+ DUTY_CYCLE = "duty_cycle"
924
+ EFFECT = "effect"
925
+ ENERGY_COUNTER = "energy_counter"
926
+ ERROR = "error"
927
+ FREQUENCY = "frequency"
928
+ GROUP_LEVEL = "group_level"
929
+ GROUP_LEVEL_2 = "group_level_2"
930
+ GROUP_STATE = "group_state"
931
+ HEATING_COOLING = "heating_cooling"
932
+ HEATING_VALVE_TYPE = "heating_valve_type"
933
+ HUE = "hue"
934
+ HUMIDITY = "humidity"
935
+ INHIBIT = "inhibit"
936
+ INTERVAL = "interval"
937
+ LEVEL = "level"
938
+ LEVEL_2 = "level_2"
939
+ LEVEL_COMBINED = "level_combined"
940
+ LOCK_STATE = "lock_state"
941
+ LOCK_TARGET_LEVEL = "lock_target_level"
942
+ LOWBAT = "lowbat"
943
+ LOWERING_MODE = "lowering_mode"
944
+ LOW_BAT = "low_bat"
945
+ LOW_BAT_LIMIT = "low_bat_limit"
946
+ MANU_MODE = "manu_mode"
947
+ MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE = "min_max_value_not_relevant_for_manu_mode"
948
+ ON_TIME_UNIT = "on_time_unit"
949
+ ON_TIME_VALUE = "on_time_value"
950
+ ON_TIME_LIST = "on_time_list_1"
951
+ OPEN = "open"
952
+ OPERATING_VOLTAGE = "operating_voltage"
953
+ OPERATION_MODE = "channel_operation_mode"
954
+ OPTICAL_ALARM_ACTIVE = "optical_alarm_active"
955
+ OPTICAL_ALARM_SELECTION = "optical_alarm_selection"
956
+ OPTIMUM_START_STOP = "optimum_start_stop"
957
+ PARTY_MODE = "party_mode"
958
+ POWER = "power"
959
+ PROGRAM = "program"
960
+ RAMP_TIME_TO_OFF_UNIT = "ramp_time_to_off_unit"
961
+ RAMP_TIME_TO_OFF_VALUE = "ramp_time_to_off_value"
962
+ RAMP_TIME_UNIT = "ramp_time_unit"
963
+ RAMP_TIME_VALUE = "ramp_time_value"
964
+ REPETITIONS = "repetitions"
965
+ RSSI_DEVICE = "rssi_device"
966
+ RSSI_PEER = "rssi_peer"
967
+ SABOTAGE = "sabotage"
968
+ SATURATION = "saturation"
969
+ SECTION = "section"
970
+ SETPOINT = "setpoint"
971
+ SET_POINT_MODE = "set_point_mode"
972
+ SMOKE_DETECTOR_ALARM_STATUS = "smoke_detector_alarm_status"
973
+ SMOKE_DETECTOR_COMMAND = "smoke_detector_command"
974
+ SOUNDFILE = "soundfile"
975
+ STATE = "state"
976
+ STOP = "stop"
977
+ SWITCH_MAIN = "switch_main"
978
+ SWITCH_V1 = "vswitch_1"
979
+ SWITCH_V2 = "vswitch_2"
980
+ TEMPERATURE = "temperature"
981
+ TEMPERATURE_MAXIMUM = "temperature_maximum"
982
+ TEMPERATURE_MINIMUM = "temperature_minimum"
983
+ TEMPERATURE_OFFSET = "temperature_offset"
984
+ VALVE_STATE = "valve_state"
985
+ VOLTAGE = "voltage"
986
+ WEEK_PROGRAM_POINTER = "week_program_pointer"
987
+
988
+
989
+ class Flag(IntEnum):
990
+ """Enum with Homematic flags."""
991
+
992
+ VISIBLE = 1
993
+ INTERNAL = 2
994
+ TRANSFORM = 4 # not used
995
+ SERVICE = 8
996
+ STICKY = 10 # This might be wrong. Documentation says 0x10 # not used
997
+
998
+
999
+ class ForcedDeviceAvailability(StrEnum):
1000
+ """Enum with aiohomematic event types."""
1001
+
1002
+ FORCE_FALSE = "forced_not_available"
1003
+ FORCE_TRUE = "forced_available"
1004
+ NOT_SET = "not_set"
1005
+
1006
+
1007
+ class InternalCustomID(StrEnum):
1008
+ """Enum for Homematic internal custom IDs."""
1009
+
1010
+ DEFAULT = "cid_default"
1011
+ LINK_PEER = "cid_link_peer"
1012
+ MANU_TEMP = "cid_manu_temp"
1013
+
1014
+
1015
+ class Manufacturer(StrEnum):
1016
+ """Enum with aiohomematic system events."""
1017
+
1018
+ EQ3 = "eQ-3"
1019
+ HB = "Homebrew"
1020
+ MOEHLENHOFF = "Möhlenhoff"
1021
+
1022
+
1023
+ class Operations(IntEnum):
1024
+ """Enum with Homematic operations."""
1025
+
1026
+ NONE = 0 # not used
1027
+ READ = 1
1028
+ WRITE = 2
1029
+ EVENT = 4
1030
+
1031
+
1032
+ class OptionalSettings(StrEnum):
1033
+ """Enum with aiohomematic optional settings."""
1034
+
1035
+ ASYNC_RPC_SERVER = "ASYNC_RPC_SERVER"
1036
+ """Use async XML-RPC server instead of thread-based (opt-in)."""
1037
+
1038
+ ENABLE_LINKED_ENTITY_CLIMATE_ACTIVITY = "ENABLE_LINKED_ENTITY_CLIMATE_ACTIVITY"
1039
+ SR_DISABLE_RANDOMIZE_OUTPUT = "SR_DISABLE_RANDOMIZED_OUTPUT"
1040
+ SR_RECORD_SYSTEM_INIT = "SR_RECORD_SYSTEM_INIT"
1041
+
1042
+ USE_INTERFACE_CLIENT = "USE_INTERFACE_CLIENT"
1043
+ """Use InterfaceClient with Backend Strategy Pattern instead of legacy clients (opt-in)."""
1044
+
1045
+
1046
+ class Parameter(StrEnum):
1047
+ """Enum with Homematic parameters."""
1048
+
1049
+ ACOUSTIC_ALARM_ACTIVE = "ACOUSTIC_ALARM_ACTIVE"
1050
+ ACOUSTIC_ALARM_SELECTION = "ACOUSTIC_ALARM_SELECTION"
1051
+ ACOUSTIC_NOTIFICATION_SELECTION = "ACOUSTIC_NOTIFICATION_SELECTION"
1052
+ ACTIVE_PROFILE = "ACTIVE_PROFILE"
1053
+ ACTIVITY_STATE = "ACTIVITY_STATE"
1054
+ ACTUAL_HUMIDITY = "ACTUAL_HUMIDITY"
1055
+ ACTUAL_TEMPERATURE = "ACTUAL_TEMPERATURE"
1056
+ AUTO_MODE = "AUTO_MODE"
1057
+ BATTERY_STATE = "BATTERY_STATE"
1058
+ BOOST_MODE = "BOOST_MODE"
1059
+ BURST_LIMIT_WARNING = "BURST_LIMIT_WARNING"
1060
+ BUTTON_LOCK = "BUTTON_LOCK"
1061
+ CHANNEL_COLOR = "CHANNEL_COLOR"
1062
+ CHANNEL_LOCK = "CHANNEL_LOCK"
1063
+ CHANNEL_OPERATION_MODE = "CHANNEL_OPERATION_MODE"
1064
+ COLOR = "COLOR"
1065
+ COLOR_BEHAVIOUR = "COLOR_BEHAVIOUR"
1066
+ COLOR_TEMPERATURE = "COLOR_TEMPERATURE"
1067
+ COMBINED_PARAMETER = "COMBINED_PARAMETER"
1068
+ COMFORT_MODE = "COMFORT_MODE"
1069
+ CONCENTRATION = "CONCENTRATION"
1070
+ CONFIG_PENDING = "CONFIG_PENDING"
1071
+ CONTROL_MODE = "CONTROL_MODE"
1072
+ CURRENT = "CURRENT"
1073
+ CURRENT_ILLUMINATION = "CURRENT_ILLUMINATION"
1074
+ DEVICE_OPERATION_MODE = "DEVICE_OPERATION_MODE"
1075
+ DIRECTION = "DIRECTION"
1076
+ DIRT_LEVEL = "DIRT_LEVEL"
1077
+ DISPLAY_DATA_ALIGNMENT = "DISPLAY_DATA_ALIGNMENT"
1078
+ DISPLAY_DATA_BACKGROUND_COLOR = "DISPLAY_DATA_BACKGROUND_COLOR"
1079
+ DISPLAY_DATA_ICON = "DISPLAY_DATA_ICON"
1080
+ DISPLAY_DATA_TEXT_COLOR = "DISPLAY_DATA_TEXT_COLOR"
1081
+ DISPLAY_DATA_COMMIT = "DISPLAY_DATA_COMMIT"
1082
+ DISPLAY_DATA_ID = "DISPLAY_DATA_ID"
1083
+ DISPLAY_DATA_STRING = "DISPLAY_DATA_STRING"
1084
+ DOOR_COMMAND = "DOOR_COMMAND"
1085
+ DOOR_STATE = "DOOR_STATE"
1086
+ DURATION_UNIT = "DURATION_UNIT"
1087
+ DURATION_VALUE = "DURATION_VALUE"
1088
+ DUTYCYCLE = "DUTYCYCLE"
1089
+ DUTY_CYCLE = "DUTY_CYCLE"
1090
+ EFFECT = "EFFECT"
1091
+ ENERGY_COUNTER = "ENERGY_COUNTER"
1092
+ ENERGY_COUNTER_FEED_IN = "ENERGY_COUNTER_FEED_IN"
1093
+ ERROR = "ERROR"
1094
+ ERROR_JAMMED = "ERROR_JAMMED"
1095
+ FREQUENCY = "FREQUENCY"
1096
+ GLOBAL_BUTTON_LOCK = "GLOBAL_BUTTON_LOCK"
1097
+ HEATING_COOLING = "HEATING_COOLING"
1098
+ HEATING_VALVE_TYPE = "HEATING_VALVE_TYPE"
1099
+ HUE = "HUE"
1100
+ HUMIDITY = "HUMIDITY"
1101
+ ILLUMINATION = "ILLUMINATION"
1102
+ INTERVAL = "INTERVAL"
1103
+ LED_STATUS = "LED_STATUS"
1104
+ LEVEL = "LEVEL"
1105
+ LEVEL_2 = "LEVEL_2"
1106
+ LEVEL_COMBINED = "LEVEL_COMBINED"
1107
+ LEVEL_SLATS = "LEVEL_SLATS"
1108
+ LOCK_STATE = "LOCK_STATE"
1109
+ LOCK_TARGET_LEVEL = "LOCK_TARGET_LEVEL"
1110
+ LOWBAT = "LOWBAT"
1111
+ LOWERING_MODE = "LOWERING_MODE"
1112
+ LOW_BAT = "LOW_BAT"
1113
+ LOW_BAT_LIMIT = "LOW_BAT_LIMIT"
1114
+ MANU_MODE = "MANU_MODE"
1115
+ MASS_CONCENTRATION_PM_10_24H_AVERAGE = "MASS_CONCENTRATION_PM_10_24H_AVERAGE"
1116
+ MASS_CONCENTRATION_PM_1_24H_AVERAGE = "MASS_CONCENTRATION_PM_1_24H_AVERAGE"
1117
+ MASS_CONCENTRATION_PM_2_5_24H_AVERAGE = "MASS_CONCENTRATION_PM_2_5_24H_AVERAGE"
1118
+ MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE = "MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE"
1119
+ MOTION = "MOTION"
1120
+ MOTION_DETECTION_ACTIVE = "MOTION_DETECTION_ACTIVE"
1121
+ ON_TIME = "ON_TIME"
1122
+ ON_TIME_LIST_1 = "ON_TIME_LIST_1"
1123
+ OPEN = "OPEN"
1124
+ OPERATING_VOLTAGE = "OPERATING_VOLTAGE"
1125
+ OPTICAL_ALARM_ACTIVE = "OPTICAL_ALARM_ACTIVE"
1126
+ OPTICAL_ALARM_SELECTION = "OPTICAL_ALARM_SELECTION"
1127
+ OPTIMUM_START_STOP = "OPTIMUM_START_STOP"
1128
+ PARTY_MODE = "PARTY_MODE"
1129
+ PARTY_MODE_SUBMIT = "PARTY_MODE_SUBMIT"
1130
+ PARTY_TIME_END = "PARTY_TIME_END"
1131
+ PARTY_TIME_START = "PARTY_TIME_START"
1132
+ PONG = "PONG"
1133
+ POWER = "POWER"
1134
+ PRESS = "PRESS"
1135
+ PRESS_CONT = "PRESS_CONT"
1136
+ PRESS_LOCK = "PRESS_LOCK"
1137
+ PRESS_LONG = "PRESS_LONG"
1138
+ PRESS_LONG_RELEASE = "PRESS_LONG_RELEASE"
1139
+ PRESS_LONG_START = "PRESS_LONG_START"
1140
+ PRESS_SHORT = "PRESS_SHORT"
1141
+ PRESS_UNLOCK = "PRESS_UNLOCK"
1142
+ PROGRAM = "PROGRAM"
1143
+ RAMP_TIME = "RAMP_TIME"
1144
+ RAMP_TIME_TO_OFF_UNIT = "RAMP_TIME_TO_OFF_UNIT"
1145
+ RAMP_TIME_TO_OFF_VALUE = "RAMP_TIME_TO_OFF_VALUE"
1146
+ RAMP_TIME_UNIT = "RAMP_TIME_UNIT"
1147
+ RAMP_TIME_VALUE = "RAMP_TIME_VALUE"
1148
+ REPETITIONS = "REPETITIONS"
1149
+ RESET_MOTION = "RESET_MOTION"
1150
+ RSSI_DEVICE = "RSSI_DEVICE"
1151
+ RSSI_PEER = "RSSI_PEER"
1152
+ SABOTAGE = "SABOTAGE"
1153
+ SATURATION = "SATURATION"
1154
+ SECTION = "SECTION"
1155
+ SENSOR = "SENSOR"
1156
+ SENSOR_ERROR = "SENSOR_ERROR"
1157
+ SEQUENCE_OK = "SEQUENCE_OK"
1158
+ SETPOINT = "SETPOINT"
1159
+ SET_POINT_MODE = "SET_POINT_MODE"
1160
+ SET_POINT_TEMPERATURE = "SET_POINT_TEMPERATURE"
1161
+ SET_TEMPERATURE = "SET_TEMPERATURE"
1162
+ SMOKE_DETECTOR_ALARM_STATUS = "SMOKE_DETECTOR_ALARM_STATUS"
1163
+ SMOKE_DETECTOR_COMMAND = "SMOKE_DETECTOR_COMMAND"
1164
+ SMOKE_LEVEL = "SMOKE_LEVEL"
1165
+ SOUNDFILE = "SOUNDFILE"
1166
+ STATE = "STATE"
1167
+ STATUS = "STATUS"
1168
+ STICKY_UN_REACH = "STICKY_UNREACH"
1169
+ STOP = "STOP"
1170
+ SUNSHINE_DURATION = "SUNSHINEDURATION"
1171
+ TEMPERATURE = "TEMPERATURE"
1172
+ TEMPERATURE_MAXIMUM = "TEMPERATURE_MAXIMUM"
1173
+ TEMPERATURE_MINIMUM = "TEMPERATURE_MINIMUM"
1174
+ TEMPERATURE_OFFSET = "TEMPERATURE_OFFSET"
1175
+ TIME_OF_OPERATION = "TIME_OF_OPERATION"
1176
+ UN_REACH = "UNREACH"
1177
+ UPDATE_PENDING = "UPDATE_PENDING"
1178
+ VALVE_STATE = "VALVE_STATE"
1179
+ VOLTAGE = "VOLTAGE"
1180
+ WATER_FLOW = "WATER_FLOW"
1181
+ WATER_VOLUME = "WATER_VOLUME"
1182
+ WATER_VOLUME_SINCE_OPEN = "WATER_VOLUME_SINCE_OPEN"
1183
+ WEEK_PROGRAM_POINTER = "WEEK_PROGRAM_POINTER"
1184
+ WIND_DIRECTION = "WIND_DIRECTION"
1185
+ WIND_DIRECTION_RANGE = "WIND_DIRECTION_RANGE"
1186
+ WIND_SPEED = "WIND_SPEED"
1187
+ WORKING = "WORKING"
1188
+
1189
+
1190
+ class ParamsetKey(StrEnum):
1191
+ """Enum with paramset keys."""
1192
+
1193
+ CALCULATED = "CALCULATED"
1194
+ DUMMY = "DUMMY"
1195
+ LINK = "LINK"
1196
+ MASTER = "MASTER"
1197
+ SERVICE = "SERVICE"
1198
+ VALUES = "VALUES"
1199
+
1200
+
1201
+ class ProductGroup(StrEnum):
1202
+ """Enum with Homematic product groups."""
1203
+
1204
+ HM = "BidCos-RF"
1205
+ HMIP = "HmIP-RF"
1206
+ HMIPW = "HmIP-Wired"
1207
+ HMW = "BidCos-Wired"
1208
+ UNKNOWN = "unknown"
1209
+ VIRTUAL = "VirtualDevices"
1210
+
1211
+
1212
+ class RegaScript(StrEnum):
1213
+ """Enum with Homematic rega scripts."""
1214
+
1215
+ ACCEPT_DEVICE_IN_INBOX = "accept_device_in_inbox.fn"
1216
+ CREATE_BACKUP_START = "create_backup_start.fn"
1217
+ CREATE_BACKUP_STATUS = "create_backup_status.fn"
1218
+ FETCH_ALL_DEVICE_DATA = "fetch_all_device_data.fn"
1219
+ GET_BACKEND_INFO = "get_backend_info.fn"
1220
+ GET_INBOX_DEVICES = "get_inbox_devices.fn"
1221
+ GET_PROGRAM_DESCRIPTIONS = "get_program_descriptions.fn"
1222
+ GET_SERIAL = "get_serial.fn"
1223
+ GET_SERVICE_MESSAGES = "get_service_messages.fn"
1224
+ GET_SYSTEM_UPDATE_INFO = "get_system_update_info.fn"
1225
+ GET_SYSTEM_VARIABLE_DESCRIPTIONS = "get_system_variable_descriptions.fn"
1226
+ SET_PROGRAM_STATE = "set_program_state.fn"
1227
+ SET_SYSTEM_VARIABLE = "set_system_variable.fn"
1228
+ TRIGGER_FIRMWARE_UPDATE = "trigger_firmware_update.fn"
1229
+
1230
+
1231
+ class RPCType(StrEnum):
1232
+ """Enum with Homematic rpc types."""
1233
+
1234
+ XML_RPC = "xmlrpc"
1235
+ JSON_RPC = "jsonrpc"
1236
+
1237
+
1238
+ class Interface(StrEnum):
1239
+ """Enum with Homematic interfaces."""
1240
+
1241
+ BIDCOS_RF = "BidCos-RF"
1242
+ BIDCOS_WIRED = "BidCos-Wired"
1243
+ CCU_JACK = "CCU-Jack"
1244
+ CUXD = "CUxD"
1245
+ HMIP_RF = "HmIP-RF"
1246
+ VIRTUAL_DEVICES = "VirtualDevices"
1247
+
1248
+
1249
+ class PingPongMismatchType(StrEnum):
1250
+ """Enum for PING/PONG mismatch event types."""
1251
+
1252
+ PENDING = "pending" # PING sent but no PONG received
1253
+ UNKNOWN = "unknown" # PONG received without matching PING
1254
+
1255
+
1256
+ class IntegrationIssueSeverity(StrEnum):
1257
+ """Severity level for integration issues."""
1258
+
1259
+ ERROR = "error"
1260
+ WARNING = "warning"
1261
+
1262
+
1263
+ class IntegrationIssueType(StrEnum):
1264
+ """
1265
+ Type of integration issue.
1266
+
1267
+ Each value serves as both:
1268
+ - issue_id prefix (e.g., "ping_pong_mismatch_{interface_id}")
1269
+ - translation_key (e.g., "ping_pong_mismatch")
1270
+ """
1271
+
1272
+ PING_PONG_MISMATCH = "ping_pong_mismatch"
1273
+ FETCH_DATA_FAILED = "fetch_data_failed"
1274
+
1275
+
1276
+ class DataRefreshType(StrEnum):
1277
+ """Type of data refresh operation."""
1278
+
1279
+ CLIENT_DATA = "client_data"
1280
+ CONNECTIVITY = "connectivity"
1281
+ INBOX = "inbox"
1282
+ METRICS = "metrics"
1283
+ PROGRAM = "program"
1284
+ SYSTEM_UPDATE = "system_update"
1285
+ SYSVAR = "sysvar"
1286
+
1287
+
1288
+ class ProgramTrigger(StrEnum):
1289
+ """Trigger source for program execution."""
1290
+
1291
+ API = "api"
1292
+ USER = "user"
1293
+ SCHEDULER = "scheduler"
1294
+ AUTOMATION = "automation"
1295
+
1296
+
1297
+ class ParameterType(StrEnum):
1298
+ """Enum for Homematic parameter types."""
1299
+
1300
+ ACTION = "ACTION" # Usually buttons, send Boolean to trigger
1301
+ BOOL = "BOOL"
1302
+ DUMMY = "DUMMY"
1303
+ ENUM = "ENUM"
1304
+ FLOAT = "FLOAT"
1305
+ INTEGER = "INTEGER"
1306
+ STRING = "STRING"
1307
+ EMPTY = ""
1308
+
1309
+
1310
+ class ProxyInitState(Enum):
1311
+ """Enum with proxy handling results."""
1312
+
1313
+ INIT_FAILED = 0
1314
+ INIT_SUCCESS = 1
1315
+ DE_INIT_FAILED = 4
1316
+ DE_INIT_SUCCESS = 8
1317
+ DE_INIT_SKIPPED = 16
1318
+
1319
+
1320
+ class ClientState(StrEnum):
1321
+ """
1322
+ Client connection lifecycle states.
1323
+
1324
+ State Machine
1325
+ -------------
1326
+ The client follows this state machine:
1327
+
1328
+ ```
1329
+ CREATED ─────► INITIALIZING ─────► INITIALIZED
1330
+ │ │
1331
+ │ (failure) │
1332
+ ▼ ▼
1333
+ FAILED CONNECTING ◄────┐
1334
+ ▲ │ │
1335
+ │ │ │ (re-connect)
1336
+ │ (failure) │ │
1337
+ │ ▼ │
1338
+ └───────────── CONNECTED │
1339
+ │ │
1340
+ ┌─────────────┼────────┼──┐
1341
+ │ │ │ │
1342
+ ▼ ▼ │ ▼
1343
+ DISCONNECTED RECONNECTING──┘ STOPPING
1344
+ │ │ │
1345
+ └─────────────┘ ▼
1346
+ STOPPED
1347
+ ```
1348
+
1349
+ Valid Transitions
1350
+ -----------------
1351
+ - CREATED → INITIALIZING
1352
+ - INITIALIZING → INITIALIZED | FAILED
1353
+ - INITIALIZED → CONNECTING
1354
+ - CONNECTING → CONNECTED | FAILED
1355
+ - CONNECTED → DISCONNECTED | RECONNECTING | STOPPING
1356
+ - DISCONNECTED → CONNECTING | RECONNECTING | STOPPING
1357
+ - RECONNECTING → CONNECTED | DISCONNECTED | FAILED | CONNECTING
1358
+ - STOPPING → STOPPED
1359
+ - FAILED → INITIALIZING (retry)
1360
+ """
1361
+
1362
+ CREATED = "created"
1363
+ INITIALIZING = "initializing"
1364
+ INITIALIZED = "initialized"
1365
+ CONNECTING = "connecting"
1366
+ CONNECTED = "connected"
1367
+ DISCONNECTED = "disconnected"
1368
+ RECONNECTING = "reconnecting"
1369
+ STOPPING = "stopping"
1370
+ STOPPED = "stopped"
1371
+ FAILED = "failed"
1372
+
1373
+
1374
+ class CircuitState(StrEnum):
1375
+ """Circuit breaker states."""
1376
+
1377
+ CLOSED = "closed"
1378
+ """Normal operation - requests are allowed through."""
1379
+
1380
+ OPEN = "open"
1381
+ """Failure mode - requests are immediately rejected."""
1382
+
1383
+ HALF_OPEN = "half_open"
1384
+ """Test mode - one request is allowed to test recovery."""
1385
+
1386
+
1387
+ class RpcServerType(StrEnum):
1388
+ """Enum for Homematic rpc server types."""
1389
+
1390
+ XML_RPC = "xml_rpc"
1391
+ NONE = "none"
1392
+
1393
+
1394
+ class RxMode(IntEnum):
1395
+ """Enum for Homematic rx modes."""
1396
+
1397
+ UNDEFINED = 0
1398
+ ALWAYS = 1
1399
+ BURST = 2
1400
+ CONFIG = 4
1401
+ WAKEUP = 8
1402
+ LAZY_CONFIG = 16
1403
+
1404
+
1405
+ class ServiceMessageType(IntEnum):
1406
+ """Enum for CCU service message types (AlType)."""
1407
+
1408
+ GENERIC = 0
1409
+ STICKY = 1
1410
+ CONFIG_PENDING = 2
1411
+
1412
+
1413
+ class SourceOfDeviceCreation(StrEnum):
1414
+ """Enum with source of device creation."""
1415
+
1416
+ CACHE = "CACHE"
1417
+ INIT = "INIT"
1418
+ MANUAL = "MANUAL"
1419
+ NEW = "NEW"
1420
+ REFRESH = "REFRESH"
1421
+
1422
+
1423
+ class HubValueType(StrEnum):
1424
+ """Enum for Homematic hub value types."""
1425
+
1426
+ ALARM = "ALARM"
1427
+ FLOAT = "FLOAT"
1428
+ INTEGER = "INTEGER"
1429
+ LIST = "LIST"
1430
+ LOGIC = "LOGIC"
1431
+ NUMBER = "NUMBER"
1432
+ STRING = "STRING"
1433
+
1434
+
1435
+ CLICK_EVENTS: Final[frozenset[Parameter]] = frozenset(
1436
+ {
1437
+ Parameter.PRESS,
1438
+ Parameter.PRESS_CONT,
1439
+ Parameter.PRESS_LOCK,
1440
+ Parameter.PRESS_LONG,
1441
+ Parameter.PRESS_LONG_RELEASE,
1442
+ Parameter.PRESS_LONG_START,
1443
+ Parameter.PRESS_SHORT,
1444
+ Parameter.PRESS_UNLOCK,
1445
+ }
1446
+ )
1447
+
1448
+ DEVICE_ERROR_EVENTS: Final[tuple[Parameter, ...]] = (Parameter.ERROR, Parameter.SENSOR_ERROR)
1449
+
1450
+ DATA_POINT_EVENTS: Final[frozenset[DeviceTriggerEventType]] = frozenset(
1451
+ {
1452
+ DeviceTriggerEventType.IMPULSE,
1453
+ DeviceTriggerEventType.KEYPRESS,
1454
+ }
1455
+ )
1456
+
1457
+
1458
+ type DP_KEY_VALUE = tuple[DataPointKey, Any]
1459
+ type SYSVAR_TYPE = bool | float | int | str | None
1460
+
1461
+ HMIP_FIRMWARE_UPDATE_IN_PROGRESS_STATES: Final[frozenset[DeviceFirmwareState]] = frozenset(
1462
+ {
1463
+ DeviceFirmwareState.DO_UPDATE_PENDING,
1464
+ DeviceFirmwareState.PERFORMING_UPDATE,
1465
+ }
1466
+ )
1467
+
1468
+ HMIP_FIRMWARE_UPDATE_READY_STATES: Final[frozenset[DeviceFirmwareState]] = frozenset(
1469
+ {
1470
+ DeviceFirmwareState.READY_FOR_UPDATE,
1471
+ DeviceFirmwareState.DO_UPDATE_PENDING,
1472
+ DeviceFirmwareState.PERFORMING_UPDATE,
1473
+ }
1474
+ )
1475
+
1476
+ IMPULSE_EVENTS: Final[frozenset[Parameter]] = frozenset({Parameter.SEQUENCE_OK})
1477
+
1478
+ KEY_CHANNEL_OPERATION_MODE_VISIBILITY: Final[Mapping[str, frozenset[str]]] = MappingProxyType(
1479
+ {
1480
+ Parameter.STATE: frozenset({"BINARY_BEHAVIOR"}),
1481
+ Parameter.PRESS_LONG: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
1482
+ Parameter.PRESS_LONG_RELEASE: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
1483
+ Parameter.PRESS_LONG_START: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
1484
+ Parameter.PRESS_SHORT: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
1485
+ }
1486
+ )
1487
+
1488
+ BLOCKED_CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
1489
+ DataPointCategory.ACTION,
1490
+ DataPointCategory.ACTION_SELECT,
1491
+ )
1492
+
1493
+ HUB_CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
1494
+ DataPointCategory.HUB_BINARY_SENSOR,
1495
+ DataPointCategory.HUB_BUTTON,
1496
+ DataPointCategory.HUB_NUMBER,
1497
+ DataPointCategory.HUB_SELECT,
1498
+ DataPointCategory.HUB_SENSOR,
1499
+ DataPointCategory.HUB_SWITCH,
1500
+ DataPointCategory.HUB_TEXT,
1501
+ DataPointCategory.HUB_UPDATE,
1502
+ )
1503
+
1504
+ CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
1505
+ DataPointCategory.ACTION_SELECT,
1506
+ DataPointCategory.BINARY_SENSOR,
1507
+ DataPointCategory.BUTTON,
1508
+ DataPointCategory.CLIMATE,
1509
+ DataPointCategory.COVER,
1510
+ DataPointCategory.EVENT,
1511
+ DataPointCategory.LIGHT,
1512
+ DataPointCategory.LOCK,
1513
+ DataPointCategory.NUMBER,
1514
+ DataPointCategory.SELECT,
1515
+ DataPointCategory.SENSOR,
1516
+ DataPointCategory.SIREN,
1517
+ DataPointCategory.SWITCH,
1518
+ DataPointCategory.TEXT,
1519
+ DataPointCategory.TEXT_DISPLAY,
1520
+ DataPointCategory.UPDATE,
1521
+ DataPointCategory.VALVE,
1522
+ )
1523
+
1524
+ PRIMARY_CLIENT_CANDIDATE_INTERFACES: Final[frozenset[Interface]] = frozenset(
1525
+ {
1526
+ Interface.HMIP_RF,
1527
+ Interface.BIDCOS_RF,
1528
+ Interface.BIDCOS_WIRED,
1529
+ }
1530
+ )
1531
+
1532
+ RELEVANT_INIT_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
1533
+ {
1534
+ Parameter.CONFIG_PENDING,
1535
+ Parameter.STICKY_UN_REACH,
1536
+ Parameter.UN_REACH,
1537
+ }
1538
+ )
1539
+
1540
+ INTERFACES_SUPPORTING_FIRMWARE_UPDATES: Final[frozenset[Interface]] = frozenset(
1541
+ {
1542
+ Interface.BIDCOS_RF,
1543
+ Interface.BIDCOS_WIRED,
1544
+ Interface.HMIP_RF,
1545
+ }
1546
+ )
1547
+
1548
+ INTERFACES_REQUIRING_XML_RPC: Final[frozenset[Interface]] = frozenset(
1549
+ {
1550
+ Interface.BIDCOS_RF,
1551
+ Interface.BIDCOS_WIRED,
1552
+ Interface.HMIP_RF,
1553
+ Interface.VIRTUAL_DEVICES,
1554
+ }
1555
+ )
1556
+
1557
+
1558
+ INTERFACES_SUPPORTING_RPC_CALLBACK: Final[frozenset[Interface]] = frozenset(INTERFACES_REQUIRING_XML_RPC)
1559
+
1560
+
1561
+ INTERFACES_REQUIRING_JSON_RPC_CLIENT: Final[frozenset[Interface]] = frozenset(
1562
+ {
1563
+ Interface.CUXD,
1564
+ Interface.CCU_JACK,
1565
+ }
1566
+ )
1567
+
1568
+ DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH: Final[frozenset[Interface]] = frozenset(
1569
+ INTERFACES_REQUIRING_JSON_RPC_CLIENT - INTERFACES_REQUIRING_XML_RPC
1570
+ )
1571
+
1572
+ INTERFACE_RPC_SERVER_TYPE: Final[Mapping[Interface, RpcServerType]] = MappingProxyType(
1573
+ {
1574
+ Interface.BIDCOS_RF: RpcServerType.XML_RPC,
1575
+ Interface.BIDCOS_WIRED: RpcServerType.XML_RPC,
1576
+ Interface.HMIP_RF: RpcServerType.XML_RPC,
1577
+ Interface.VIRTUAL_DEVICES: RpcServerType.XML_RPC,
1578
+ Interface.CUXD: RpcServerType.NONE,
1579
+ Interface.CCU_JACK: RpcServerType.NONE,
1580
+ }
1581
+ )
1582
+
1583
+ LINKABLE_INTERFACES: Final[frozenset[Interface]] = frozenset(
1584
+ {
1585
+ Interface.BIDCOS_RF,
1586
+ Interface.BIDCOS_WIRED,
1587
+ Interface.HMIP_RF,
1588
+ }
1589
+ )
1590
+
1591
+
1592
+ DEFAULT_USE_PERIODIC_SCAN_FOR_INTERFACES: Final = True
1593
+
1594
+ IGNORE_FOR_UN_IGNORE_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
1595
+ {
1596
+ Parameter.CONFIG_PENDING,
1597
+ Parameter.STICKY_UN_REACH,
1598
+ Parameter.UN_REACH,
1599
+ }
1600
+ )
1601
+
1602
+
1603
+ # Ignore Parameter on initial load that end with
1604
+ _IGNORE_ON_INITIAL_LOAD_PARAMETERS_END_RE: Final = re.compile(r".*(_ERROR)$")
1605
+ # Ignore Parameter on initial load that start with
1606
+ _IGNORE_ON_INITIAL_LOAD_PARAMETERS_START_RE: Final = re.compile(r"^(ERROR_|RSSI_)")
1607
+ _IGNORE_ON_INITIAL_LOAD_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
1608
+ {
1609
+ Parameter.DUTY_CYCLE,
1610
+ Parameter.DUTYCYCLE,
1611
+ Parameter.LOW_BAT,
1612
+ Parameter.LOWBAT,
1613
+ Parameter.OPERATING_VOLTAGE,
1614
+ }
1615
+ )
1616
+
1617
+ _CLIMATE_SOURCE_ROLES: Final[tuple[str, ...]] = ("CLIMATE",)
1618
+ _CLIMATE_TARGET_ROLES: Final[tuple[str, ...]] = ("CLIMATE", "SWITCH", "LEVEL")
1619
+ _CLIMATE_TRANSMITTER_RE: Final = re.compile(r"(?:CLIMATE|HEATING).*(?:TRANSMITTER|TRANSCEIVER)")
1620
+ _CLIMATE_RECEIVER_RE: Final = re.compile(r"(?:CLIMATE|HEATING).*(?:TRANSCEIVER|RECEIVER)")
1621
+
1622
+
1623
+ def get_link_source_categories(
1624
+ *, source_roles: tuple[str, ...], channel_type_name: str
1625
+ ) -> tuple[DataPointCategory, ...]:
1626
+ """Return the channel sender roles."""
1627
+ result: set[DataPointCategory] = set()
1628
+ has_climate = False
1629
+ if _CLIMATE_TRANSMITTER_RE.search(channel_type_name):
1630
+ result.add(DataPointCategory.CLIMATE)
1631
+ has_climate = True
1632
+
1633
+ if not has_climate and source_roles and any("CLIMATE" in role for role in source_roles):
1634
+ result.add(DataPointCategory.CLIMATE)
1635
+
1636
+ return tuple(result)
1637
+
1638
+
1639
+ def get_link_target_categories(
1640
+ *, target_roles: tuple[str, ...], channel_type_name: str
1641
+ ) -> tuple[DataPointCategory, ...]:
1642
+ """Return the channel receiver roles."""
1643
+ result: set[DataPointCategory] = set()
1644
+ has_climate = False
1645
+ if _CLIMATE_RECEIVER_RE.search(channel_type_name):
1646
+ result.add(DataPointCategory.CLIMATE)
1647
+ has_climate = True
1648
+
1649
+ if (
1650
+ not has_climate
1651
+ and target_roles
1652
+ and any(cl_role in role for role in target_roles for cl_role in _CLIMATE_TARGET_ROLES)
1653
+ ):
1654
+ result.add(DataPointCategory.CLIMATE)
1655
+
1656
+ return tuple(result)
1657
+
1658
+
1659
+ RECEIVER_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
1660
+ {
1661
+ Parameter.LEVEL,
1662
+ Parameter.STATE,
1663
+ }
1664
+ )
1665
+
1666
+
1667
+ def check_ignore_parameter_on_initial_load(*, parameter: str) -> bool:
1668
+ """Check if a parameter matches common wildcard patterns."""
1669
+ return (
1670
+ bool(_IGNORE_ON_INITIAL_LOAD_PARAMETERS_START_RE.match(parameter))
1671
+ or bool(_IGNORE_ON_INITIAL_LOAD_PARAMETERS_END_RE.match(parameter))
1672
+ or parameter in _IGNORE_ON_INITIAL_LOAD_PARAMETERS
1673
+ )
1674
+
1675
+
1676
+ # Ignore Parameter on initial load that start with
1677
+ _IGNORE_ON_INITIAL_LOAD_MODEL_START_RE: Final = re.compile(r"^(HmIP-SWSD)")
1678
+ _IGNORE_ON_INITIAL_LOAD_MODEL: Final = ("HmIP-SWD",)
1679
+ _IGNORE_ON_INITIAL_LOAD_MODEL_LOWER: Final = tuple(model.lower() for model in _IGNORE_ON_INITIAL_LOAD_MODEL)
1680
+
1681
+
1682
+ def check_ignore_model_on_initial_load(*, model: str) -> bool:
1683
+ """Check if a model matches common wildcard patterns."""
1684
+ return (
1685
+ bool(_IGNORE_ON_INITIAL_LOAD_MODEL_START_RE.match(model))
1686
+ or model.lower() in _IGNORE_ON_INITIAL_LOAD_MODEL_LOWER
1687
+ )
1688
+
1689
+
1690
+ # virtual remotes s
1691
+ VIRTUAL_REMOTE_MODELS: Final[tuple[str, ...]] = (
1692
+ "HM-RCV-50",
1693
+ "HMW-RCV-50",
1694
+ "HmIP-RCV-50",
1695
+ )
1696
+
1697
+ VIRTUAL_REMOTE_ADDRESSES: Final[tuple[str, ...]] = (
1698
+ "BidCoS-RF",
1699
+ "BidCoS-Wir",
1700
+ "HmIP-RCV-1",
1701
+ )
1702
+
1703
+
1704
+ @dataclass(frozen=True, kw_only=True, slots=True)
1705
+ class HubData:
1706
+ """Dataclass for hub data points."""
1707
+
1708
+ legacy_name: str
1709
+ enabled_default: bool = False
1710
+ description: str | None = None
1711
+
1712
+
1713
+ @dataclass(frozen=True, kw_only=True, slots=True)
1714
+ class ProgramData(HubData):
1715
+ """Dataclass for programs."""
1716
+
1717
+ pid: str
1718
+ is_active: bool
1719
+ is_internal: bool
1720
+ last_execute_time: str
1721
+
1722
+
1723
+ @dataclass(frozen=True, kw_only=True, slots=True)
1724
+ class SystemVariableData(HubData):
1725
+ """Dataclass for system variables."""
1726
+
1727
+ vid: str
1728
+ value: SYSVAR_TYPE
1729
+ data_type: HubValueType | None = None
1730
+ extended_sysvar: bool = False
1731
+ max_value: float | int | None = None
1732
+ min_value: float | int | None = None
1733
+ unit: str | None = None
1734
+ values: tuple[str, ...] | None = None
1735
+
1736
+
1737
+ @dataclass(frozen=True, kw_only=True, slots=True)
1738
+ class InstallModeData:
1739
+ """Dataclass for install mode data points."""
1740
+
1741
+ name: str
1742
+ interface: Interface
1743
+
1744
+
1745
+ @dataclass(frozen=True, kw_only=True, slots=True)
1746
+ class SystemInformation:
1747
+ """
1748
+ System information of the backend.
1749
+
1750
+ CCU types:
1751
+ - CCU: Original CCU2/CCU3 hardware and debmatic (CCU clone)
1752
+ - OPENCCU: OpenCCU (modern variants)
1753
+ """
1754
+
1755
+ available_interfaces: tuple[str, ...] = field(default_factory=tuple)
1756
+ auth_enabled: bool | None = None
1757
+ https_redirect_enabled: bool | None = None
1758
+ serial: str | None = None
1759
+ # Backend info fields
1760
+ version: str = ""
1761
+ hostname: str = ""
1762
+ ccu_type: CCUType = CCUType.UNKNOWN
1763
+
1764
+ @property
1765
+ def has_backup(self) -> bool:
1766
+ """Return True if backend supports online firmware update checks."""
1767
+ return self.ccu_type == CCUType.OPENCCU
1768
+
1769
+
1770
+ @dataclass(frozen=True, kw_only=True, slots=True)
1771
+ class InboxDeviceData:
1772
+ """Dataclass for inbox devices."""
1773
+
1774
+ device_id: str
1775
+ address: str
1776
+ name: str
1777
+ device_type: str
1778
+ interface: str
1779
+
1780
+
1781
+ @dataclass(frozen=True, kw_only=True, slots=True)
1782
+ class ServiceMessageData:
1783
+ """Dataclass for service messages."""
1784
+
1785
+ msg_id: str
1786
+ name: str
1787
+ timestamp: str
1788
+ msg_type: int
1789
+ address: str = ""
1790
+ device_name: str = ""
1791
+
1792
+
1793
+ @dataclass(frozen=True, kw_only=True, slots=True)
1794
+ class SystemUpdateData:
1795
+ """Dataclass for system update information."""
1796
+
1797
+ current_firmware: str
1798
+ available_firmware: str
1799
+ update_available: bool
1800
+ check_script_available: bool = False
1801
+
1802
+
1803
+ class BackupStatus(StrEnum):
1804
+ """Enum with backup status values."""
1805
+
1806
+ IDLE = "idle"
1807
+ RUNNING = "running"
1808
+ COMPLETED = "completed"
1809
+ FAILED = "failed"
1810
+
1811
+
1812
+ @dataclass(frozen=True, kw_only=True, slots=True)
1813
+ class BackupStatusData:
1814
+ """Dataclass for backup status information."""
1815
+
1816
+ status: BackupStatus
1817
+ file_path: str = ""
1818
+ filename: str = ""
1819
+ size: int = 0
1820
+
1821
+
1822
+ @dataclass(frozen=True, kw_only=True, slots=True)
1823
+ class BackupData:
1824
+ """Dataclass for backup download result."""
1825
+
1826
+ filename: str
1827
+ content: bytes
1828
+
1829
+
1830
+ class ParameterData(TypedDict, total=False):
1831
+ """Typed dict for parameter data."""
1832
+
1833
+ DEFAULT: Any
1834
+ FLAGS: int
1835
+ ID: str
1836
+ MAX: Any
1837
+ MIN: Any
1838
+ OPERATIONS: int
1839
+ SPECIAL: Mapping[str, Any]
1840
+ TYPE: ParameterType
1841
+ UNIT: str
1842
+ VALUE_LIST: Iterable[str]
1843
+
1844
+
1845
+ class DeviceDescription(TypedDict, total=False):
1846
+ """
1847
+ Typed dict for device descriptions.
1848
+
1849
+ Based on HM_XmlRpc_API.pdf V2.16 and HMIP_XmlRpc_API_Addendum.pdf V2.10.
1850
+ """
1851
+
1852
+ # Required fields per API spec
1853
+ TYPE: Required[str]
1854
+ ADDRESS: Required[str]
1855
+ PARAMSETS: Required[list[str]]
1856
+ # Optional fields - Common
1857
+ CHILDREN: list[str]
1858
+ PARENT: str | None
1859
+ PARENT_TYPE: str | None
1860
+ SUBTYPE: str | None
1861
+ # Optional fields - Firmware
1862
+ FIRMWARE: str | None
1863
+ AVAILABLE_FIRMWARE: str | None
1864
+ UPDATABLE: bool
1865
+ FIRMWARE_UPDATE_STATE: str | None
1866
+ FIRMWARE_UPDATABLE: bool | None
1867
+ # Optional fields - Interface/Connectivity
1868
+ INTERFACE: str | None
1869
+ RX_MODE: int | None
1870
+ # Optional fields - Links
1871
+ LINK_SOURCE_ROLES: str | None
1872
+ LINK_TARGET_ROLES: str | None
1873
+ # Optional fields - Device metadata
1874
+ RF_ADDRESS: int | None
1875
+ INDEX: int | None
1876
+ AES_ACTIVE: int | None
1877
+ VERSION: int | None
1878
+ FLAGS: int | None
1879
+ DIRECTION: int | None
1880
+ # Optional fields - Groups/Teams
1881
+ GROUP: str | None
1882
+ TEAM: str | None
1883
+ TEAM_TAG: str | None
1884
+ TEAM_CHANNELS: list[str] | None
1885
+ ROAMING: int | None
1886
+
1887
+
1888
+ class ChannelDetail(TypedDict):
1889
+ """Typed dict for channel details from JSON-RPC Device.listAllDetail."""
1890
+
1891
+ address: str
1892
+ name: str
1893
+ id: int
1894
+
1895
+
1896
+ class DeviceDetail(TypedDict):
1897
+ """Typed dict for device details from JSON-RPC Device.listAllDetail."""
1898
+
1899
+ address: str
1900
+ name: str
1901
+ id: int
1902
+ interface: str
1903
+ channels: list[ChannelDetail]
1904
+
1905
+
1906
+ # Interface default ports mapping
1907
+ _INTERFACE_DEFAULT_PORTS: Final[dict[str, tuple[int, int]]] = {
1908
+ "BidCos-RF": DETECTION_PORT_BIDCOS_RF,
1909
+ "BidCos-Wired": DETECTION_PORT_BIDCOS_WIRED,
1910
+ "HmIP-RF": DETECTION_PORT_HMIP_RF,
1911
+ "VirtualDevices": DETECTION_PORT_VIRTUAL_DEVICES,
1912
+ }
1913
+
1914
+
1915
+ def get_interface_default_port(*, interface: Interface | str, tls: bool) -> int | None:
1916
+ """
1917
+ Get the default port for an interface based on TLS setting.
1918
+
1919
+ Args:
1920
+ interface: The interface (Interface enum or string value).
1921
+ tls: Whether TLS is enabled.
1922
+
1923
+ Returns:
1924
+ The default port number, or None if the interface has no default port
1925
+ (e.g., CCU-Jack, CUxD which don't use XML-RPC ports).
1926
+
1927
+ Example:
1928
+ >>> get_interface_default_port(Interface.HMIP_RF, tls=False)
1929
+ 2010
1930
+ >>> get_interface_default_port(Interface.HMIP_RF, tls=True)
1931
+ 42010
1932
+ >>> get_interface_default_port(Interface.CCU_JACK, tls=True)
1933
+ None
1934
+
1935
+ """
1936
+ interface_key = interface.value if isinstance(interface, Interface) else interface
1937
+ if ports := _INTERFACE_DEFAULT_PORTS.get(interface_key):
1938
+ return ports[1] if tls else ports[0]
1939
+ return None
1940
+
1941
+
1942
+ def get_json_rpc_default_port(*, tls: bool) -> int:
1943
+ """
1944
+ Get the default JSON-RPC port based on TLS setting.
1945
+
1946
+ Args:
1947
+ tls: Whether TLS is enabled.
1948
+
1949
+ Returns:
1950
+ The default JSON-RPC port (443 for TLS, 80 for non-TLS).
1951
+
1952
+ """
1953
+ return DEFAULT_JSON_RPC_TLS_PORT if tls else DEFAULT_JSON_RPC_PORT
1954
+
1955
+
1956
+ def is_interface_default_port(*, interface: Interface | str, port: int) -> bool:
1957
+ """
1958
+ Check if a port is a default port (TLS or non-TLS) for the given interface.
1959
+
1960
+ Args:
1961
+ interface: The interface (Interface enum or string value).
1962
+ port: The port number to check.
1963
+
1964
+ Returns:
1965
+ True if the port is either the TLS or non-TLS default for this interface.
1966
+
1967
+ """
1968
+ interface_key = interface.value if isinstance(interface, Interface) else interface
1969
+ if ports := _INTERFACE_DEFAULT_PORTS.get(interface_key):
1970
+ return port in ports
1971
+ return False
1972
+
1973
+
1974
+ class AstroType(IntEnum):
1975
+ """Enum for astro event types."""
1976
+
1977
+ SUNRISE = 0
1978
+ SUNSET = 1
1979
+
1980
+
1981
+ class ScheduleActorChannel(IntEnum):
1982
+ """Enum for target actor channels (bitwise)."""
1983
+
1984
+ CHANNEL_1_1 = 1
1985
+ CHANNEL_1_2 = 2
1986
+ CHANNEL_1_3 = 4
1987
+ CHANNEL_2_1 = 8
1988
+ CHANNEL_2_2 = 16
1989
+ CHANNEL_2_3 = 32
1990
+ CHANNEL_3_1 = 64
1991
+ CHANNEL_3_2 = 128
1992
+ CHANNEL_3_3 = 256
1993
+ CHANNEL_4_1 = 512
1994
+ CHANNEL_4_2 = 1024
1995
+ CHANNEL_4_3 = 2048
1996
+ CHANNEL_5_1 = 4096
1997
+ CHANNEL_5_2 = 8192
1998
+ CHANNEL_5_3 = 16384
1999
+ CHANNEL_6_1 = 32768
2000
+ CHANNEL_6_2 = 65536
2001
+ CHANNEL_6_3 = 131072
2002
+ CHANNEL_7_1 = 262144
2003
+ CHANNEL_7_2 = 524288
2004
+ CHANNEL_7_3 = 1048576
2005
+ CHANNEL_8_1 = 2097152
2006
+ CHANNEL_8_2 = 4194304
2007
+ CHANNEL_8_3 = 8388608
2008
+
2009
+
2010
+ class ScheduleCondition(IntEnum):
2011
+ """Enum for schedule trigger conditions."""
2012
+
2013
+ FIXED_TIME = 0
2014
+ ASTRO = 1
2015
+ FIXED_IF_BEFORE_ASTRO = 2
2016
+ ASTRO_IF_BEFORE_FIXED = 3
2017
+ FIXED_IF_AFTER_ASTRO = 4
2018
+ ASTRO_IF_AFTER_FIXED = 5
2019
+ EARLIEST_OF_FIXED_AND_ASTRO = 6
2020
+ LATEST_OF_FIXED_AND_ASTRO = 7
2021
+
2022
+
2023
+ class ScheduleField(StrEnum):
2024
+ """Enum for switch schedule field names."""
2025
+
2026
+ ASTRO_OFFSET = "ASTRO_OFFSET"
2027
+ ASTRO_TYPE = "ASTRO_TYPE"
2028
+ CONDITION = "CONDITION"
2029
+ DURATION_BASE = "DURATION_BASE"
2030
+ DURATION_FACTOR = "DURATION_FACTOR"
2031
+ FIXED_HOUR = "FIXED_HOUR"
2032
+ FIXED_MINUTE = "FIXED_MINUTE"
2033
+ LEVEL = "LEVEL"
2034
+ LEVEL_2 = "LEVEL_2"
2035
+ RAMP_TIME_BASE = "RAMP_TIME_BASE"
2036
+ RAMP_TIME_FACTOR = "RAMP_TIME_FACTOR"
2037
+ TARGET_CHANNELS = "TARGET_CHANNELS"
2038
+ WEEKDAY = "WEEKDAY"
2039
+
2040
+
2041
+ class ScheduleSlotType(StrEnum):
2042
+ """Enum for climate item type."""
2043
+
2044
+ ENDTIME = "ENDTIME"
2045
+ STARTTIME = "STARTTIME"
2046
+ TEMPERATURE = "TEMPERATURE"
2047
+
2048
+
2049
+ class ScheduleProfile(StrEnum):
2050
+ """Enum for climate profiles."""
2051
+
2052
+ P1 = "P1"
2053
+ P2 = "P2"
2054
+ P3 = "P3"
2055
+ P4 = "P4"
2056
+ P5 = "P5"
2057
+ P6 = "P6"
2058
+
2059
+
2060
+ class TimeBase(IntEnum):
2061
+ """Enum for duration base units."""
2062
+
2063
+ MS_100 = 0 # 100 milliseconds
2064
+ SEC_1 = 1 # 1 second
2065
+ SEC_5 = 2 # 5 seconds
2066
+ SEC_10 = 3 # 10 seconds
2067
+ MIN_1 = 4 # 1 minute
2068
+ MIN_5 = 5 # 5 minutes
2069
+ MIN_10 = 6 # 10 minutes
2070
+ HOUR_1 = 7 # 1 hour
2071
+
2072
+
2073
+ class WeekdayInt(IntEnum):
2074
+ """Enum for weekdays (bitwise)."""
2075
+
2076
+ SUNDAY = 1
2077
+ MONDAY = 2
2078
+ TUESDAY = 4
2079
+ WEDNESDAY = 8
2080
+ THURSDAY = 16
2081
+ FRIDAY = 32
2082
+ SATURDAY = 64
2083
+
2084
+
2085
+ class WeekdayStr(StrEnum):
2086
+ """Enum for climate week days."""
2087
+
2088
+ MONDAY = "MONDAY"
2089
+ TUESDAY = "TUESDAY"
2090
+ WEDNESDAY = "WEDNESDAY"
2091
+ THURSDAY = "THURSDAY"
2092
+ FRIDAY = "FRIDAY"
2093
+ SATURDAY = "SATURDAY"
2094
+ SUNDAY = "SUNDAY"
2095
+
2096
+
2097
+ CLIMATE_MAX_SCHEDULER_TIME: Final = "24:00"
2098
+ CLIMATE_MIN_SCHEDULER_TIME: Final = "00:00"
2099
+ CLIMATE_RELEVANT_SLOT_TYPES: Final = ("endtime", "temperature")
2100
+
2101
+
2102
+ class ScheduleSlot(TypedDict):
2103
+ """
2104
+ A single time slot in a climate schedule.
2105
+
2106
+ Each slot defines when a temperature period ends and what temperature to maintain.
2107
+ Climate devices use 13 slots per weekday, with unused slots filled with "24:00".
2108
+
2109
+ Attributes:
2110
+ endtime: End time in "HH:MM" format (e.g., "06:00", "24:00")
2111
+ temperature: Target temperature in degrees Celsius
2112
+
2113
+ Example:
2114
+ {"endtime": "06:00", "temperature": 18.0}
2115
+
2116
+ """
2117
+
2118
+ endtime: str
2119
+ temperature: float
2120
+
2121
+
2122
+ ClimateWeekdaySchedule = dict[int, ScheduleSlot]
2123
+ """Schedule slots for a single weekday, keyed by slot number (1-13)."""
2124
+
2125
+ ClimateProfileSchedule = dict[WeekdayStr, ClimateWeekdaySchedule]
2126
+ """Schedule for all weekdays in a profile."""
2127
+
2128
+ ClimateScheduleDict = dict[ScheduleProfile, ClimateProfileSchedule]
2129
+ """Complete schedule with all profiles (P1-P6)."""
2130
+ CLIMATE_SCHEDULE_SLOT_IN_RANGE: Final = range(1, 14)
2131
+ CLIMATE_SCHEDULE_SLOT_RANGE: Final = range(1, 13)
2132
+ CLIMATE_SCHEDULE_TIME_RANGE: Final = range(1441)
2133
+
2134
+
2135
+ class SimpleSchedulePeriod(TypedDict):
2136
+ """
2137
+ A single temperature period in a simple schedule.
2138
+
2139
+ Uses lowercase string keys for JSON serialization compatibility with custom cards.
2140
+
2141
+ Attributes:
2142
+ starttime: Start time in "HH:MM" format (e.g., "06:00")
2143
+ endtime: End time in "HH:MM" format (e.g., "22:00")
2144
+ temperature: Target temperature in degrees Celsius
2145
+
2146
+ """
2147
+
2148
+ starttime: str
2149
+ endtime: str
2150
+ temperature: float
2151
+
2152
+
2153
+ class SimpleWeekdaySchedule(TypedDict):
2154
+ """
2155
+ Schedule for a single weekday with base temperature and heating periods.
2156
+
2157
+ Attributes:
2158
+ base_temperature: Default temperature when no period is active
2159
+ periods: List of temperature periods with start/end times
2160
+
2161
+ Example:
2162
+ {
2163
+ "base_temperature": 18.0,
2164
+ "periods": [
2165
+ {"starttime": "06:00", "endtime": "08:00", "temperature": 21.0},
2166
+ {"starttime": "17:00", "endtime": "22:00", "temperature": 21.0}
2167
+ ]
2168
+ }
2169
+
2170
+ """
2171
+
2172
+ base_temperature: float
2173
+ periods: list[SimpleSchedulePeriod]
2174
+
2175
+
2176
+ # Type aliases for higher-level structures
2177
+ SimpleProfileSchedule = dict[WeekdayStr, SimpleWeekdaySchedule]
2178
+ """Schedule for all weekdays in a profile."""
2179
+
2180
+ SimpleScheduleDict = dict[ScheduleProfile, SimpleProfileSchedule]
2181
+ """Complete schedule with all profiles."""
2182
+
2183
+ DEFAULT_CLIMATE_FILL_TEMPERATURE: Final = 18.0
2184
+ DEFAULT_SCHEDULE_GROUP = dict[ScheduleField, Any]
2185
+ DEFAULT_SCHEDULE_DICT = dict[int, DEFAULT_SCHEDULE_GROUP]
2186
+ RAW_SCHEDULE_DICT = dict[str, float | int]
2187
+ SCHEDULE_PATTERN: Final = re.compile(r"^\d+_WP_")
2188
+
2189
+
2190
+ # Define public API for this module
2191
+ __all__ = tuple(
2192
+ sorted(
2193
+ name
2194
+ for name, obj in globals().items()
2195
+ if not name.startswith("_")
2196
+ and (
2197
+ name.isupper() # constants like VERSION, patterns, defaults
2198
+ or inspect.isclass(obj) # Enums, dataclasses, TypedDicts, NamedTuple classes
2199
+ or inspect.isfunction(obj) # module functions
2200
+ )
2201
+ and (
2202
+ getattr(obj, "__module__", __name__) == __name__
2203
+ if not isinstance(obj, int | float | str | bytes | tuple | frozenset | dict)
2204
+ else True
2205
+ )
2206
+ )
2207
+ )