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,251 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2026
3
+ """
4
+ Device profile registry for custom data point configurations.
5
+
6
+ This module provides a centralized registry for mapping device models to their
7
+ custom data point configurations, replacing the distributed ALL_DEVICES pattern.
8
+
9
+ Key types:
10
+ - DeviceConfig: Configuration for a specific device model
11
+ - ExtendedDeviceConfig: Extended configuration with additional fields
12
+ - DeviceProfileRegistry: Central registry class for device profile configurations
13
+
14
+ Example usage:
15
+ from aiohomematic.model.custom import (
16
+ DeviceProfileRegistry,
17
+ DeviceConfig,
18
+ )
19
+
20
+ # Register a device
21
+ DeviceProfileRegistry.register(
22
+ category=DataPointCategory.CLIMATE,
23
+ models=("HmIP-BWTH", "HmIP-STH"),
24
+ data_point_class=CustomDpIpThermostat,
25
+ profile_type=DeviceProfile.IP_THERMOSTAT,
26
+ schedule_channel_no=1,
27
+ )
28
+
29
+ # Get configurations for a model
30
+ configs = DeviceProfileRegistry.get_configs(model="HmIP-BWTH")
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from collections.abc import Mapping
36
+ from dataclasses import dataclass
37
+ from typing import TYPE_CHECKING, ClassVar
38
+
39
+ from aiohomematic.const import DataPointCategory, DeviceProfile, Field, Parameter
40
+
41
+ if TYPE_CHECKING:
42
+ from aiohomematic.model.custom.data_point import CustomDataPoint
43
+
44
+ __all__ = [
45
+ "DeviceConfig",
46
+ "DeviceProfileRegistry",
47
+ "ExtendedDeviceConfig",
48
+ ]
49
+
50
+
51
+ @dataclass(frozen=True, kw_only=True, slots=True)
52
+ class ExtendedDeviceConfig:
53
+ """Extended configuration for custom data point creation."""
54
+
55
+ fixed_channel_fields: Mapping[int, Mapping[Field, Parameter]] | None = None
56
+ additional_data_points: Mapping[int | tuple[int, ...], tuple[Parameter, ...]] | None = None
57
+
58
+ @property
59
+ def required_parameters(self) -> tuple[Parameter, ...]:
60
+ """Return required parameters from extended config."""
61
+ required_parameters: list[Parameter] = []
62
+ if fixed_channels := self.fixed_channel_fields:
63
+ for mapping in fixed_channels.values():
64
+ required_parameters.extend(mapping.values())
65
+
66
+ if additional_dps := self.additional_data_points:
67
+ for parameters in additional_dps.values():
68
+ required_parameters.extend(parameters)
69
+
70
+ return tuple(required_parameters)
71
+
72
+
73
+ @dataclass(frozen=True, kw_only=True, slots=True)
74
+ class DeviceConfig:
75
+ """Configuration for mapping a device model to its custom data point implementation."""
76
+
77
+ data_point_class: type[CustomDataPoint]
78
+ profile_type: DeviceProfile
79
+ channels: tuple[int | None, ...] = (1,)
80
+ extended: ExtendedDeviceConfig | None = None
81
+ schedule_channel_no: int | None = None
82
+
83
+
84
+ class DeviceProfileRegistry:
85
+ """Central registry for device profile configurations."""
86
+
87
+ _configs: ClassVar[dict[DataPointCategory, dict[str, DeviceConfig | tuple[DeviceConfig, ...]]]] = {}
88
+ _blacklist: ClassVar[set[str]] = set()
89
+
90
+ @classmethod
91
+ def blacklist(cls, *models: str) -> None:
92
+ """Blacklist device models."""
93
+ cls._blacklist.update(m.lower().replace("hb-", "hm-") for m in models)
94
+
95
+ @classmethod
96
+ def clear(cls) -> None:
97
+ """Clear all registrations. Primarily for testing."""
98
+ cls._configs.clear()
99
+ cls._blacklist.clear()
100
+
101
+ @classmethod
102
+ def get_all_configs(
103
+ cls,
104
+ *,
105
+ category: DataPointCategory,
106
+ ) -> Mapping[str, DeviceConfig | tuple[DeviceConfig, ...]]:
107
+ """Return all configurations for a category."""
108
+ return cls._configs.get(category, {})
109
+
110
+ @classmethod
111
+ def get_all_extended_configs(cls) -> tuple[ExtendedDeviceConfig, ...]:
112
+ """Return all extended configurations from all categories."""
113
+ extended_configs: list[ExtendedDeviceConfig] = []
114
+ for category_configs in cls._configs.values():
115
+ for device_config in category_configs.values():
116
+ if isinstance(device_config, tuple):
117
+ extended_configs.extend(cfg.extended for cfg in device_config if cfg.extended)
118
+ elif device_config.extended:
119
+ extended_configs.append(device_config.extended)
120
+ return tuple(extended_configs)
121
+
122
+ @classmethod
123
+ def get_blacklist(cls) -> tuple[str, ...]:
124
+ """Return current blacklist entries."""
125
+ return tuple(sorted(cls._blacklist))
126
+
127
+ @classmethod
128
+ def get_configs(
129
+ cls,
130
+ *,
131
+ model: str,
132
+ category: DataPointCategory | None = None,
133
+ ) -> tuple[DeviceConfig, ...]:
134
+ """
135
+ Return device configurations for a model.
136
+
137
+ Model matching algorithm (hierarchical, first-match wins):
138
+ 1. Normalize model name (lowercase, replace "hb-" with "hm-")
139
+ 2. Check blacklist - return empty if blacklisted
140
+ 3. For each category, try matching in order:
141
+ a. Exact match: model == registered_key
142
+ b. Prefix match: model.startswith(registered_key)
143
+
144
+ Why prefix matching?
145
+ Homematic devices often have variants (e.g., "HmIP-BWTH-1" and "HmIP-BWTH-2")
146
+ that share the same profile. Prefix matching allows registering "hmip-bwth"
147
+ once to cover all variants, reducing duplication.
148
+
149
+ Model normalization:
150
+ - Lowercase: Makes matching case-insensitive
151
+ - "hb-" → "hm-": HomeBrew devices use "HB-" prefix but behave like "HM-" devices
152
+
153
+ Result aggregation:
154
+ A model can match multiple categories (e.g., a thermostat might have both
155
+ CLIMATE and SENSOR data points). Results from all matching categories are
156
+ combined into a single tuple.
157
+
158
+ Storage format:
159
+ Registry entries can be either:
160
+ - Single DeviceConfig: For simple devices
161
+ - Tuple of DeviceConfigs: For devices with multiple data point types
162
+ (e.g., lock + button_lock on same device)
163
+ """
164
+ # Normalize model name for consistent matching
165
+ normalized = model.lower().replace("hb-", "hm-")
166
+
167
+ # Check blacklist first (fast path for excluded devices)
168
+ if cls.is_blacklisted(model=model):
169
+ return ()
170
+
171
+ configs: list[DeviceConfig] = []
172
+
173
+ # Search specified category or all categories
174
+ categories = [category] if category else list(cls._configs.keys())
175
+
176
+ for cat in categories:
177
+ if cat not in cls._configs:
178
+ continue
179
+
180
+ # Priority 1: Exact match (most specific)
181
+ if result := cls._configs[cat].get(normalized):
182
+ if isinstance(result, tuple):
183
+ configs.extend(result)
184
+ else:
185
+ configs.append(result)
186
+ continue # Found exact match, skip prefix matching for this category
187
+
188
+ # Priority 2: Prefix match (for device variants)
189
+ for model_key, result in cls._configs[cat].items():
190
+ if normalized.startswith(model_key):
191
+ if isinstance(result, tuple):
192
+ configs.extend(result)
193
+ else:
194
+ configs.append(result)
195
+ break # First prefix match wins, stop searching this category
196
+
197
+ return tuple(configs)
198
+
199
+ @classmethod
200
+ def is_blacklisted(cls, *, model: str) -> bool:
201
+ """Check if a model is blacklisted."""
202
+ normalized = model.lower().replace("hb-", "hm-")
203
+ return any(normalized.startswith(bl) for bl in cls._blacklist)
204
+
205
+ @classmethod
206
+ def register(
207
+ cls,
208
+ *,
209
+ category: DataPointCategory,
210
+ models: str | tuple[str, ...],
211
+ data_point_class: type[CustomDataPoint],
212
+ profile_type: DeviceProfile,
213
+ channels: tuple[int | None, ...] = (1,),
214
+ extended: ExtendedDeviceConfig | None = None,
215
+ schedule_channel_no: int | None = None,
216
+ ) -> None:
217
+ """Register a device configuration."""
218
+ config = DeviceConfig(
219
+ data_point_class=data_point_class,
220
+ profile_type=profile_type,
221
+ channels=channels,
222
+ extended=extended,
223
+ schedule_channel_no=schedule_channel_no,
224
+ )
225
+
226
+ models_tuple = (models,) if isinstance(models, str) else models
227
+
228
+ if category not in cls._configs:
229
+ cls._configs[category] = {}
230
+
231
+ for model in models_tuple:
232
+ normalized = model.lower().replace("hb-", "hm-")
233
+ cls._configs[category][normalized] = config
234
+
235
+ @classmethod
236
+ def register_multiple(
237
+ cls,
238
+ *,
239
+ category: DataPointCategory,
240
+ models: str | tuple[str, ...],
241
+ configs: tuple[DeviceConfig, ...],
242
+ ) -> None:
243
+ """Register multiple configurations for the same model(s)."""
244
+ models_tuple = (models,) if isinstance(models, str) else models
245
+
246
+ if category not in cls._configs:
247
+ cls._configs[category] = {}
248
+
249
+ for model in models_tuple:
250
+ normalized = model.lower().replace("hb-", "hm-")
251
+ cls._configs[category][normalized] = configs